@orkastery/cli 0.6.0 → 0.6.2

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 (118) hide show
  1. package/adapters/claude-code/agents/ork-check.md +1 -1
  2. package/adapters/claude-code/agents/ork-go.md +1 -1
  3. package/adapters/claude-code/agents/ork-goal.md +1 -1
  4. package/adapters/claude-code/agents/ork-master.md +1 -1
  5. package/adapters/claude-code/agents/ork-plan.md +1 -1
  6. package/adapters/claude-code/agents/ork-ship.md +1 -1
  7. package/adapters/claude-code/commands/ork.md +1 -1
  8. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
  9. package/adapters/codex/skills/ork/SKILL.md +1 -1
  10. package/adapters/openclaw/openclaw.plugin.json +1 -1
  11. package/adapters/openclaw/package.json +1 -1
  12. package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
  13. package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
  14. package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
  15. package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
  16. package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
  17. package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
  18. package/assets/adapters-en/claude-code/commands/ork.md +1 -1
  19. package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
  20. package/assets/orkmind_fixture.py +4 -1
  21. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
  22. package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
  23. package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
  24. package/dist/adapters/claude-bg.js +9 -7
  25. package/dist/adapters/grok-events.js +227 -0
  26. package/dist/adapters/grok-mcp-servidor.js +622 -0
  27. package/dist/adapters/grok-mcp.js +178 -0
  28. package/dist/adapters/grok.js +686 -0
  29. package/dist/agendador-cpu.js +92 -0
  30. package/dist/branch-de-estado.js +7 -2
  31. package/dist/changelog-check.js +217 -0
  32. package/dist/check-agil.js +289 -25
  33. package/dist/check-paralelo.js +73 -18
  34. package/dist/ci.js +3 -2
  35. package/dist/claim-lint.js +29 -0
  36. package/dist/claims.js +4 -0
  37. package/dist/conducao.js +5 -0
  38. package/dist/docs.js +16 -16
  39. package/dist/doctor-forja.js +1 -3
  40. package/dist/doctor.js +1 -1
  41. package/dist/entrega-pr.js +10 -10
  42. package/dist/fabrica-estado.js +20 -11
  43. package/dist/fila-pesada.js +121 -11
  44. package/dist/hitl-classificacao.js +1 -0
  45. package/dist/horario.js +4 -1
  46. package/dist/index.js +63 -35
  47. package/dist/integracoes-locais.js +2 -0
  48. package/dist/intelligence-graph-cli.js +27 -9
  49. package/dist/locale-accounts.js +11 -3
  50. package/dist/locale-ajuda.js +10 -6
  51. package/dist/locale-claims.js +16 -2
  52. package/dist/locale-forja.js +2 -4
  53. package/dist/locale-grafo.js +56 -1
  54. package/dist/locale-maestro.js +38 -0
  55. package/dist/locale-network.js +8 -6
  56. package/dist/locale-onboarding.js +14 -0
  57. package/dist/locale-operacao-restante.js +2 -2
  58. package/dist/locale-preflight.js +80 -0
  59. package/dist/locale-projetos.js +6 -0
  60. package/dist/locale-prompts.js +8 -8
  61. package/dist/locale-pulse.js +4 -0
  62. package/dist/locale-retry.js +56 -2
  63. package/dist/locale-security-scan.js +35 -1
  64. package/dist/locale-ship.js +10 -2
  65. package/dist/locale-verify.js +15 -0
  66. package/dist/locale.js +24 -7
  67. package/dist/maestro-cli.js +12 -17
  68. package/dist/manifest.js +6 -1
  69. package/dist/mcp-install.js +24 -13
  70. package/dist/mcp-server.js +62 -33
  71. package/dist/mcp-verify-async.js +353 -0
  72. package/dist/mcp-verify-worker.js +17 -0
  73. package/dist/mcp-verify.js +10 -1
  74. package/dist/merge-da-entrega.js +85 -0
  75. package/dist/network-roadmap.js +18 -14
  76. package/dist/noctum-aceite.js +154 -14
  77. package/dist/noctum-cli.js +69 -12
  78. package/dist/noctum-entrega.js +55 -7
  79. package/dist/noctum-envelope.js +1 -1
  80. package/dist/noctum-execucao.js +5 -1
  81. package/dist/noctum-fechamento.js +18 -7
  82. package/dist/noctum-fila.js +6 -1
  83. package/dist/noctum-guarda.js +81 -4
  84. package/dist/noctum-maquinas.js +5 -1
  85. package/dist/noctum-modo.js +35 -7
  86. package/dist/noctum-reconciliacao.js +4 -1
  87. package/dist/noctum-rede.js +102 -0
  88. package/dist/noctum-relatorio.js +44 -10
  89. package/dist/noctum-retomada-rede.js +71 -0
  90. package/dist/noctum-vigia.js +49 -9
  91. package/dist/parado-no-condutor.js +14 -15
  92. package/dist/pedido-conducao.js +51 -0
  93. package/dist/phase.js +43 -15
  94. package/dist/postura-local.js +7 -3
  95. package/dist/preflight.js +27 -27
  96. package/dist/prompts.js +4 -2
  97. package/dist/pulse.js +4 -2
  98. package/dist/ratelimit.js +1 -0
  99. package/dist/rede.js +24 -12
  100. package/dist/retry.js +115 -10
  101. package/dist/runtime-context.js +165 -45
  102. package/dist/runtime-escada.js +7 -1
  103. package/dist/runtime-profiles.js +27 -17
  104. package/dist/runtimes.js +42 -5
  105. package/dist/security-scan/agil.js +18 -7
  106. package/dist/security-scan/cli.js +2 -2
  107. package/dist/security-scan/executor.js +8 -0
  108. package/dist/security-scan/report.js +1 -1
  109. package/dist/session-watcher-grok.js +425 -0
  110. package/dist/session-watcher.js +52 -11
  111. package/dist/sessoes-contas.js +33 -5
  112. package/dist/setup.js +11 -11
  113. package/dist/varredura-acoes.js +8 -15
  114. package/dist/verify.js +191 -43
  115. package/package.json +2 -2
  116. package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
  117. package/skills/phases/check-quality/SKILL.md +1 -1
  118. package/skills/phases/go-implementation/SKILL.md +1 -1
@@ -33,10 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MOTIVO_FORA_DOS_DESTINOS_AUTOMATICOS = exports.RUNTIMES_FORA_DOS_DESTINOS_AUTOMATICOS = exports.MOTIVO_SEM_HOST_MCP = exports.RUNTIMES_SEM_HOST_MCP = void 0;
36
37
  exports.novaIdentidadeDeDespacho = novaIdentidadeDeDespacho;
37
38
  exports.capacidadeDoRuntimeNoProjeto = capacidadeDoRuntimeNoProjeto;
39
+ exports.capacidadeDaRotacao = capacidadeDaRotacao;
38
40
  exports.contextoDoProjeto = contextoDoProjeto;
39
41
  exports.validarContextoRuntime = validarContextoRuntime;
42
+ exports.validarContextoDoGrok = validarContextoDoGrok;
40
43
  exports.lerDeclaracaoDeMod = lerDeclaracaoDeMod;
41
44
  exports.modsDeFaseInstalados = modsDeFaseInstalados;
42
45
  /** Contexto interno opt-in: configuracao M4 existente + catalogo instalado, nunca args do modelo. */
@@ -83,48 +86,89 @@ function ler(raiz, file, limite) {
83
86
  fs.closeSync(fd);
84
87
  }
85
88
  }
86
- function preparado(raiz) {
87
- let encontrado;
88
- for (const host of ['claude-code', 'codex']) {
89
- const file = path.join(raiz, host === 'codex' ? '.codex/config.toml' : '.mcp.json');
90
- if (!fs.existsSync(file))
91
- continue;
89
+ /** A entrada `orkastery` de um host, conferida contra o perfil ja achado nos hosts anteriores; sem entrada, o perfil fica. */
90
+ function perfilDoHost(raiz, host, encontrado) {
91
+ const file = (0, mcp_install_1.arquivoDoHostMcp)(raiz, host), toml = host !== 'claude-code';
92
+ if (!fs.existsSync(file))
93
+ return encontrado;
94
+ let config;
95
+ try {
92
96
  const text = ler(raiz, file, 262144).toString('utf8');
93
- const config = objeto(host === 'codex' ? (0, smol_toml_1.parse)(text) : JSON.parse(text));
94
- const chave = host === 'codex' ? 'mcp_servers' : 'mcpServers';
95
- if (config[chave] === undefined)
96
- continue;
97
- const servidores = objeto(config[chave]);
98
- if (!Object.hasOwn(servidores, 'orkastery'))
99
- continue;
100
- const entrada = objeto(servidores.orkastery), padrao = (0, mcp_install_1.configuracaoServidorMcp)(raiz, host);
101
- const legado = padrao.args.slice(0, -2);
102
- let transporte = 'github-ssh';
103
- let argv = entrada.args;
104
- let permissoesFilho = 'interactive';
105
- if (Array.isArray(argv) && argv[argv.length - 2] === '--child-permissions') {
106
- if (!['interactive', 'worktree'].includes(argv[argv.length - 1]))
107
- throw Error('runtime.context.conflict: perfil de filho invalido');
108
- permissoesFilho = argv[argv.length - 1];
109
- argv = argv.slice(0, -2);
97
+ config = objeto(toml ? (0, smol_toml_1.parse)(text) : JSON.parse(text));
98
+ }
99
+ catch (erro) {
100
+ // RM-067 (F4b, GO-FIX A4 e N2): o motivo tipado do grok leva o codigo do erro de sistema ou a primeira linha do parser,
101
+ // sem o trecho do arquivo do time nem o caminho da maquina.
102
+ if (host !== 'grok')
103
+ throw erro;
104
+ const causa = erro.code ?? erro.message.split('\n')[0];
105
+ throw Error(`runtime.context.conflict: .grok/config.toml do projeto ilegivel (${causa}); conserte o arquivo ou tire dele a entrada orkastery`);
106
+ }
107
+ const chave = toml ? 'mcp_servers' : 'mcpServers';
108
+ if (config[chave] === undefined)
109
+ return encontrado;
110
+ const servidores = objeto(config[chave]);
111
+ if (!Object.hasOwn(servidores, 'orkastery'))
112
+ return encontrado;
113
+ const entrada = objeto(servidores.orkastery), padrao = (0, mcp_install_1.configuracaoServidorMcp)(raiz, host);
114
+ const legado = padrao.args.slice(0, -2);
115
+ let transporte = 'github-ssh';
116
+ let argv = entrada.args;
117
+ let permissoesFilho = 'interactive';
118
+ if (Array.isArray(argv) && argv[argv.length - 2] === '--child-permissions') {
119
+ if (!['interactive', 'worktree'].includes(argv[argv.length - 1]))
120
+ throw Error('runtime.context.conflict: perfil de filho invalido');
121
+ permissoesFilho = argv[argv.length - 1];
122
+ argv = argv.slice(0, -2);
123
+ }
124
+ if (JSON.stringify(argv) !== JSON.stringify(legado)) {
125
+ if (!Array.isArray(argv) || argv.length !== padrao.args.length ||
126
+ argv[argv.length - 2] !== '--ship-transport' || !(0, mcp_transportes_1.ehTransporteShipMcp)(argv[argv.length - 1]))
127
+ throw Error('runtime.context.conflict: MCP Orkastery divergente; transporte invalido ou argumentos extras');
128
+ transporte = argv[argv.length - 1];
129
+ }
130
+ const esperado = (0, mcp_install_1.configuracaoServidorMcp)(raiz, host, undefined, transporte);
131
+ if (entrada.command !== esperado.command || (JSON.stringify(argv) !== JSON.stringify(esperado.args) && JSON.stringify(argv) !== JSON.stringify(legado)))
132
+ throw Error('runtime.context.conflict: MCP Orkastery divergente; repare a instalacao do projeto');
133
+ if (encontrado !== undefined && encontrado.transporteShip !== transporte)
134
+ throw Error('runtime.context.conflict: transportes SHIP divergentes entre hosts');
135
+ if (encontrado !== undefined && encontrado.permissoesFilho !== permissoesFilho)
136
+ throw Error('runtime.context.conflict: perfis de filho divergentes entre hosts');
137
+ return { ...encontrado, transporteShip: transporte, permissoesFilho, ...(host === 'codex' ? { politicasCodex: entrada } : {}) };
138
+ }
139
+ /**
140
+ * O perfil da instalacao MCP do projeto. RM-067 (F4b, GO-FIX A4): o `.grok/config.toml` tambem prepara, em TOML como o do
141
+ * codex, mas ele e a config do proprio grok, que o time pode manter: para quem nao e o grok (`quem`), a falha de leitura,
142
+ * de parse ou de conferencia dele nao derruba a preparacao dos outros hosts, e a entrada dele fica de fora. GO-FIX N1: sem
143
+ * outro host preparado, a entrada `orkastery` do grok e a preparacao do projeto, e engolir a falha dela faria o claude-bg
144
+ * e o codex despacharem como num projeto legado, sem o MCP; a falha volta, e so a config do time sem a entrada fica de fora.
145
+ */
146
+ function preparado(raiz, quem) {
147
+ let encontrado;
148
+ let falhaDoGrok = null;
149
+ for (const host of mcp_install_1.HOSTS_MCP) {
150
+ try {
151
+ encontrado = perfilDoHost(raiz, host, encontrado);
110
152
  }
111
- if (JSON.stringify(argv) !== JSON.stringify(legado)) {
112
- if (!Array.isArray(argv) || argv.length !== padrao.args.length ||
113
- argv[argv.length - 2] !== '--ship-transport' || !(0, mcp_transportes_1.ehTransporteShipMcp)(argv[argv.length - 1]))
114
- throw Error('runtime.context.conflict: MCP Orkastery divergente; transporte invalido ou argumentos extras');
115
- transporte = argv[argv.length - 1];
153
+ catch (erro) {
154
+ if (host !== 'grok' || quem === undefined || quem === 'grok')
155
+ throw erro;
156
+ falhaDoGrok = erro;
116
157
  }
117
- const esperado = (0, mcp_install_1.configuracaoServidorMcp)(raiz, host, undefined, transporte);
118
- if (entrada.command !== esperado.command || (JSON.stringify(argv) !== JSON.stringify(esperado.args) && JSON.stringify(argv) !== JSON.stringify(legado)))
119
- throw Error('runtime.context.conflict: MCP Orkastery divergente; repare a instalacao do projeto');
120
- if (encontrado !== undefined && encontrado.transporteShip !== transporte)
121
- throw Error('runtime.context.conflict: transportes SHIP divergentes entre hosts');
122
- if (encontrado !== undefined && encontrado.permissoesFilho !== permissoesFilho)
123
- throw Error('runtime.context.conflict: perfis de filho divergentes entre hosts');
124
- encontrado = { ...encontrado, transporteShip: transporte, permissoesFilho, ...(host === 'codex' ? { politicasCodex: entrada } : {}) };
125
158
  }
159
+ if (falhaDoGrok !== null && encontrado === undefined && podeTerEntradaDoOrk(raiz))
160
+ throw falhaDoGrok;
126
161
  return encontrado;
127
162
  }
163
+ /** O `.grok/config.toml` do projeto tem, ou pode ter, a entrada `orkastery`; o que nao se le conta como se tivesse. */
164
+ function podeTerEntradaDoOrk(raiz) {
165
+ try {
166
+ return ler(raiz, (0, mcp_install_1.arquivoDoHostMcp)(raiz, 'grok'), 262144).toString('utf8').includes('orkastery');
167
+ }
168
+ catch {
169
+ return true;
170
+ }
171
+ }
128
172
  function raizCanonica(projeto) {
129
173
  if (!path.isAbsolute(projeto))
130
174
  throw Error('runtime.context.invalid: projeto absoluto obrigatorio');
@@ -140,6 +184,8 @@ function hostDoRuntime(runtime) {
140
184
  return 'claude-code';
141
185
  if (runtime === 'codex')
142
186
  return 'codex';
187
+ if (runtime === 'grok')
188
+ return 'grok';
143
189
  throw Error('runtime.context.invalid: runtime desconhecido');
144
190
  }
145
191
  /** Confere o recibo e todos os bytes do adaptador antes de considerar o runtime um destino. */
@@ -186,40 +232,88 @@ function capacidadeDoRuntimeNoProjeto(projeto, runtime) {
186
232
  let host = null;
187
233
  try {
188
234
  const raiz = raizCanonica(projeto);
189
- const perfil = preparado(raiz);
235
+ const perfil = preparado(raiz, runtime);
190
236
  if (!perfil)
191
237
  return { runtime, host: null, preparado: false, capaz: true, motivo: null };
238
+ if (exports.RUNTIMES_SEM_HOST_MCP.includes(runtime))
239
+ return { runtime, host: null, preparado: true, capaz: false, motivo: exports.MOTIVO_SEM_HOST_MCP };
192
240
  host = hostDoRuntime(runtime);
193
- conferirInstalacaoDoHost(raiz, host);
241
+ // RM-067 (F4b, G6): o grok nao tem adaptador instalavel (plugin, skills, mods); o contexto dele e so o MCP.
242
+ if (host !== 'grok')
243
+ conferirInstalacaoDoHost(raiz, host);
244
+ if (exports.RUNTIMES_FORA_DOS_DESTINOS_AUTOMATICOS.includes(runtime))
245
+ return { runtime, host, preparado: true, capaz: false, motivo: exports.MOTIVO_FORA_DOS_DESTINOS_AUTOMATICOS };
194
246
  return { runtime, host, preparado: true, capaz: true, motivo: null };
195
247
  }
196
248
  catch (erro) {
197
249
  return { runtime, host, preparado: true, capaz: false, motivo: erro.message };
198
250
  }
199
251
  }
252
+ /**
253
+ * RM-067 (D15 da ork-grokbuild): runtimes homologados que ainda nao tem host MCP do Orkastery. A sessao filha deles
254
+ * despacha sem contexto, como num projeto sem entrada MCP, ate o host existir. A capacidade de rotacao
255
+ * (`capacidadeDoRuntimeNoProjeto`) nao muda: num projeto preparado, eles seguem fora dos destinos automaticos.
256
+ * RM-067 (F4b, G6): o grok saiu daqui com o host `grok`; a lista fica para o proximo runtime sem host.
257
+ */
258
+ exports.RUNTIMES_SEM_HOST_MCP = [];
259
+ /** RM-067 (F4): o motivo tipado de um runtime sem host MCP num projeto preparado. */
260
+ exports.MOTIVO_SEM_HOST_MCP = 'runtime.context.no-host: runtime sem host MCP do Orkastery; a sessao despacha sem o contexto do projeto';
261
+ /**
262
+ * RM-067 (F4b, GO-FIX A5; F4c, D9): runtimes com o host MCP que, num projeto preparado, ficam fora dos destinos
263
+ * automaticos (o fallback do retry e a escada), porque o servidor MCP do ork nao grava o estado na sessao deles e a fase
264
+ * que chega por fallback pararia sem prova. O grok ficou aqui da F4b a F4c: o sandbox dele vale para o processo inteiro e
265
+ * os filhos (G17 da RM-067), e o servidor stdio era filho dele. Desde a F4c o servidor e um processo do ork, fora do
266
+ * sandbox (`adapters/grok-mcp.ts`), e a fase do grok no piso `workspace` fecha com prova; a lista fica vazia, para o
267
+ * proximo runtime cujo MCP nao grave o estado, como `RUNTIMES_SEM_HOST_MCP`.
268
+ */
269
+ exports.RUNTIMES_FORA_DOS_DESTINOS_AUTOMATICOS = [];
270
+ exports.MOTIVO_FORA_DOS_DESTINOS_AUTOMATICOS = 'runtime.context.sandbox: o sandbox do runtime vale tambem para o servidor MCP do ' +
271
+ 'ork, que nao grava o estado; como destino automatico num projeto preparado, o runtime fica fora';
272
+ /**
273
+ * RM-067 (F4): a capacidade de um runtime para a rotacao do retry. A troca de perfil DENTRO do runtime em que a thread ja
274
+ * despachou (`mesmoRuntime`) vale para o runtime sem host MCP e para o que fica fora dos destinos automaticos: a sessao
275
+ * dele ja roda como rodava, e a outra conta nao muda isso, entao o `ork retry` troca o perfil dele como troca o dos outros
276
+ * runtimes. Como alvo de fallback num projeto preparado, ele segue incapaz. Desde a F4c, nenhum runtime homologado esta
277
+ * nas duas listas, e o grok e capaz nos dois papeis.
278
+ */
279
+ function capacidadeDaRotacao(projeto, runtime, mesmoRuntime) {
280
+ const capacidade = capacidadeDoRuntimeNoProjeto(projeto, runtime);
281
+ if (capacidade.capaz || !mesmoRuntime)
282
+ return capacidade;
283
+ const soComoDestino = (exports.RUNTIMES_SEM_HOST_MCP.includes(runtime) && capacidade.motivo === exports.MOTIVO_SEM_HOST_MCP) ||
284
+ (exports.RUNTIMES_FORA_DOS_DESTINOS_AUTOMATICOS.includes(runtime) && capacidade.motivo === exports.MOTIVO_FORA_DOS_DESTINOS_AUTOMATICOS);
285
+ return soComoDestino ? { ...capacidade, capaz: true } : capacidade;
286
+ }
200
287
  /** Projeto sem entrada MCP Orkastery continua com o contrato legado. Nao instala nem altera arquivos. */
201
288
  function contextoDoProjeto(projeto, runtime, cwd, threadId) {
202
289
  const raiz = raizCanonica(projeto);
203
- const perfil = preparado(raiz);
290
+ const perfil = preparado(raiz, runtime);
204
291
  if (!perfil)
205
292
  return undefined;
293
+ if (exports.RUNTIMES_SEM_HOST_MCP.includes(runtime))
294
+ return undefined;
206
295
  const host = hostDoRuntime(runtime);
207
296
  if (!threadId)
208
297
  throw Error('runtime.context.invalid: thread obrigatoria para sessao filha');
209
298
  const contexto = { projeto: raiz, host, threadId, transporteShip: perfil.transporteShip, permissoesFilho: perfil.permissoesFilho };
210
- validarContextoRuntime(contexto, cwd);
299
+ if (host === 'grok')
300
+ validarContextoDoGrok(contexto, cwd);
301
+ else
302
+ validarContextoRuntime(contexto, cwd);
211
303
  return contexto;
212
304
  }
213
- /** Revalida no receptor do despacho, incluindo bytes instalados e escopo real do cwd. */
214
- function validarContextoRuntime(contexto, cwd) {
215
- if (!contexto || !['claude-code', 'codex'].includes(contexto.host))
216
- throw Error('runtime.context.invalid: host desconhecido');
305
+ /**
306
+ * A conferencia do vinculo, comum a todo host: projeto preparado, perfil e transporte iguais aos da instalacao, cwd e
307
+ * thread da sessao filha, identidade do despacho e, no perfil worktree, a worktree propria da thread. RM-067 (F4b, D2):
308
+ * saiu de `validarContextoRuntime` sem mudar a ordem das conferencias, para o contexto do grok usar a mesma.
309
+ */
310
+ function conferirVinculo(contexto, cwd) {
217
311
  if (contexto.transporteShip !== undefined && !(0, mcp_transportes_1.ehTransporteShipMcp)(contexto.transporteShip))
218
312
  throw Error('runtime.context.invalid: transporte desconhecido');
219
313
  const raiz = raizCanonica(contexto.projeto);
220
314
  if (contexto.permissoesFilho !== undefined && !['interactive', 'worktree'].includes(contexto.permissoesFilho))
221
315
  throw Error('runtime.context.invalid: perfil de filho desconhecido');
222
- const perfil = preparado(raiz);
316
+ const perfil = preparado(raiz, contexto.host);
223
317
  if (!perfil)
224
318
  throw Error('runtime.context.missing: projeto nao preparado pelo instalador MCP');
225
319
  const { transporteShip, permissoesFilho } = perfil;
@@ -246,6 +340,13 @@ function validarContextoRuntime(contexto, cwd) {
246
340
  if (!(0, worktree_1.worktreesDoGit)(raiz).some(w => w.dir === cwd && w.branch === (0, worktree_1.branchDaWorktree)(thread)))
247
341
  throw Error('runtime.context.worktree: worktree nao registrada na branch da thread');
248
342
  }
343
+ return { raiz, perfil, transporteShip, permissoesFilho, thread };
344
+ }
345
+ /** Revalida no receptor do despacho, incluindo bytes instalados e escopo real do cwd. */
346
+ function validarContextoRuntime(contexto, cwd) {
347
+ if (!contexto || (contexto.host !== 'claude-code' && contexto.host !== 'codex'))
348
+ throw Error('runtime.context.invalid: host desconhecido');
349
+ const { raiz, perfil, transporteShip, permissoesFilho, thread } = conferirVinculo(contexto, cwd);
249
350
  const { instalacao, arquivos, conferidos, bootstrap } = conferirInstalacaoDoHost(raiz, contexto.host);
250
351
  const modsDeFase = contexto.host === 'claude-code' ? modsDeFaseInstalados(raiz, instalacao, arquivos, conferidos) : [];
251
352
  // Capacidade derivada do bloco atual, nunca aceita como entrada do modelo.
@@ -265,6 +366,25 @@ function validarContextoRuntime(contexto, cwd) {
265
366
  ...(contexto.host === 'codex' && permissoesFilho === 'worktree' ? (0, mcp_install_1.politicasFilhoCodex)(perfil.politicasCodex ?? {}) : {}) };
266
367
  return { projeto: raiz, instalacao, bootstrap, permissoesFilho, permiteEditarProduto, grafoMcp, servidor, modsDeFase, agentTeams };
267
368
  }
369
+ /**
370
+ * RM-067 (F4b, D2): o contexto do grok, revalidado no despacho. O vinculo e o mesmo dos outros hosts; nao ha adaptador
371
+ * instalado a conferir (o grok nao tem plugin, skills nem mods do Orkastery). Devolve o que o comando e o despacho usam:
372
+ * o perfil filho, a flag do grafo da raiz e, desde a F4c, a especificacao do servidor externo vinculado a thread e ao despacho.
373
+ */
374
+ function validarContextoDoGrok(contexto, cwd) {
375
+ if (!contexto || contexto.host !== 'grok')
376
+ throw Error('runtime.context.invalid: host grok esperado');
377
+ const { raiz, transporteShip, permissoesFilho, thread } = conferirVinculo(contexto, cwd);
378
+ // RM-067 (F4b, GO-FIX B1): a config da sessao vai no cwd; na raiz do projeto ela viraria a entrada do host grok.
379
+ if (!thread.worktree || thread.worktree === raiz || cwd === raiz)
380
+ throw Error('runtime.context.worktree: o grok com o MCP do ork exige a worktree propria da thread; a config da sessao nunca vai a raiz do projeto');
381
+ const grafoMcp = (0, mcp_grafo_1.grafoLigado)((0, manifest_1.exigirManifesto)(raiz).manifesto);
382
+ const publicVerifiers = (0, hitl_public_receipt_1.publicHitlVerifiers)();
383
+ const servidor = { projeto: raiz, threadId: contexto.threadId,
384
+ dispatchId: contexto.identidade?.dispatchId ?? null, permissoesFilho, transporteShip,
385
+ ...(publicVerifiers ? { env: { [hitl_public_receipt_1.ENV_HITL_VERIFIERS]: publicVerifiers } } : {}) };
386
+ return { projeto: raiz, threadId: contexto.threadId, permissoesFilho, grafoMcp, servidor };
387
+ }
268
388
  const DECLARACAO_OU_MANIFESTO_DE_MOD = /^mods\/[^/]+\/(?:orkastery-mod\.json|\.claude-plugin\/plugin\.json)$/;
269
389
  const DECLARACAO_DE_MOD = /^mods\/([^/]+)\/orkastery-mod\.json$/;
270
390
  const NOME_DE_MOD = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
@@ -44,6 +44,7 @@ const locale_1 = require("./locale");
44
44
  const modos_1 = require("./modos");
45
45
  const runtime_profiles_1 = require("./runtime-profiles");
46
46
  const setup_1 = require("./setup");
47
+ const runtimes_1 = require("./runtimes");
47
48
  const runtime_context_1 = require("./runtime-context");
48
49
  const thread_1 = require("./thread");
49
50
  /** Os degraus, na ordem do pedido do dono de 12/10/2026. */
@@ -163,6 +164,11 @@ function planoDoDegrau(carregado, thread, fase, n, despacho, tentados, agoraMs =
163
164
  }
164
165
  return { pulo: 'fallback-sem-destino' };
165
166
  }
167
+ // RM-067 (F3, F4): o runtime sem cota esgotada tipada (o grok) fica fora do degrau "outra conta": a escada nunca troca a
168
+ // conta dele, ate o formato da cota esgotada ser confirmado no ensaio real. Como alvo de fallback (degrau 2) ele vale no
169
+ // projeto sem o MCP do ork e, desde a F4c (o servidor do ork fora do sandbox dele), tambem no preparado.
170
+ if (runtimes_1.RUNTIMES_SEM_COTA_TIPADA.includes(despacho.runtime))
171
+ return { pulo: 'cota-sem-sinal-tipado' };
166
172
  // Degrau 3: outra conta do runtime que falhou. O diretorio de cada conta e o que separa um daemon do outro. Os perfis
167
173
  // tentados sao os efetivos (o redespacho sem perfil pedido escolhe um, e e ele que falhou); o despacho que nasceu sem
168
174
  // perfil (o runtime caiu antes da escolha) tem como conta de origem a que o degrau 1 de fato usou.
@@ -187,7 +193,7 @@ function planoDoDegrau(carregado, thread, fase, n, despacho, tentados, agoraMs =
187
193
  if (p.id === contaDeOrigem?.id || jaTentados.has(p.id))
188
194
  return false;
189
195
  const d = (0, runtime_profiles_1.perfilDeDespacho)(p);
190
- return !dirOriginal || (d.configDir ?? d.codexHome) !== dirOriginal;
196
+ return !dirOriginal || (0, runtime_profiles_1.diretorioDoPerfil)(d) !== dirOriginal;
191
197
  });
192
198
  return outro ? { destino: { ...original, perfil: outro.id } } : { pulo: 'sem-outro-perfil' };
193
199
  }
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.COLUNAS_DE_PERFIS = exports.POLITICA_PADRAO = exports.ARQUIVO_CONTAS = exports.CONTRATO_CONTAS = exports.ESTADOS_DE_PERFIL = exports.RUNTIMES_COM_PERFIL = exports.VARIAVEL_DO_PERFIL = exports.ARQUIVO_PERFIS = exports.CONTRATO_PERFIS = void 0;
36
+ exports.COLUNAS_DE_PERFIS = exports.POLITICA_PADRAO = exports.ARQUIVO_CONTAS = exports.CONTRATO_CONTAS = exports.ESTADOS_DE_PERFIL = exports.RUNTIMES_COM_INVENTARIO = exports.CAMPO_DO_PERFIL = exports.RUNTIMES_COM_PERFIL = exports.VARIAVEL_DO_PERFIL = exports.ARQUIVO_PERFIS = exports.CONTRATO_PERFIS = void 0;
37
37
  exports.runtimeComPerfil = runtimeComPerfil;
38
38
  exports.validarDiretorioDePerfil = validarDiretorioDePerfil;
39
39
  exports.validarIdDePerfil = validarIdDePerfil;
@@ -81,7 +81,7 @@ exports.perfisComHorario = perfisComHorario;
81
81
  * I-33 (D3): perfis de conta por runtime, contrato `ork.runtime-profiles/v1`.
82
82
  *
83
83
  * Um perfil aponta o DIRETORIO onde o proprio CLI do runtime guarda configuracao e login
84
- * (`CLAUDE_CONFIG_DIR` no claude-bg, `CODEX_HOME` no codex). O `ork` nunca le, copia nem
84
+ * (`CLAUDE_CONFIG_DIR` no claude-bg, `CODEX_HOME` no codex, `GROK_HOME` no grok). O `ork` nunca le, copia nem
85
85
  * migra o que o CLI grava la dentro: o login e feito pelo proprio CLI com o env do perfil, e
86
86
  * o store guarda so identidade, diretorio e estado de uso. Nenhum segredo entra aqui.
87
87
  *
@@ -101,8 +101,19 @@ const locale_1 = require("./locale");
101
101
  exports.CONTRATO_PERFIS = 'ork.runtime-profiles/v1';
102
102
  exports.ARQUIVO_PERFIS = 'runtime-profiles.json';
103
103
  /** Runtimes homologados que aceitam perfil, e a variavel que cada um le. */
104
- exports.VARIAVEL_DO_PERFIL = { 'claude-bg': 'CLAUDE_CONFIG_DIR', codex: 'CODEX_HOME' };
104
+ exports.VARIAVEL_DO_PERFIL = { 'claude-bg': 'CLAUDE_CONFIG_DIR', codex: 'CODEX_HOME', grok: 'GROK_HOME' };
105
105
  exports.RUNTIMES_COM_PERFIL = Object.keys(exports.VARIAVEL_DO_PERFIL);
106
+ /** O campo do perfil que guarda o diretorio de cada runtime (o store e o despacho usam o mesmo nome). */
107
+ exports.CAMPO_DO_PERFIL = { 'claude-bg': 'configDir', codex: 'codexHome', grok: 'grokHome' };
108
+ const CAMPOS_DE_DIRETORIO = Object.values(exports.CAMPO_DO_PERFIL);
109
+ /** O diretorio padrao de cada CLI na HOME, quando o processo nao informa a variavel. */
110
+ const PASTA_PADRAO = { 'claude-bg': '.claude', codex: '.codex', grok: '.grok' };
111
+ /**
112
+ * RM-067 (F3, F4): runtimes de assinatura que o #Noctum mede e cuja conta do processo o inventario sempre consulta
113
+ * (`claude agents`, rollouts do codex). Desde a F4, o `ork sessions` e a carga do rodizio leem tambem o `grok`, pela
114
+ * pasta de sessoes que ele grava (`RUNTIMES_DAS_SESSOES` em `sessoes-contas.ts`); a medicao do #Noctum segue sem ele.
115
+ */
116
+ exports.RUNTIMES_COM_INVENTARIO = ['claude-bg', 'codex'];
106
117
  /**
107
118
  * I-33 (D13): `provider-pago` e o perfil cujo proprio CLI diz que o login e por API key,
108
119
  * `api_key_helper`, Console ou provider de nuvem, e nao pela assinatura. Nunca recebe despacho;
@@ -135,8 +146,11 @@ function validarIdDePerfil(id) {
135
146
  }
136
147
  /** Diretorio do perfil, pelo campo do seu runtime. */
137
148
  function diretorioDoPerfil(p) {
138
- const dir = p.runtime === 'claude-bg' ? p.configDir : p.codexHome;
139
- return validarDiretorioDePerfil(dir);
149
+ return validarDiretorioDePerfil(p[exports.CAMPO_DO_PERFIL[p.runtime]]);
150
+ }
151
+ /** O perfil de despacho com o diretorio no campo do seu runtime. */
152
+ function comDiretorio(id, runtime, dir) {
153
+ return { id, runtime, [exports.CAMPO_DO_PERFIL[runtime]]: dir };
140
154
  }
141
155
  /** Valida um perfil de despacho vindo do ledger, do thread.json ou do pedido da fila. */
142
156
  function validarPerfilDeDespacho(v) {
@@ -146,16 +160,13 @@ function validarPerfilDeDespacho(v) {
146
160
  const runtime = o.runtime;
147
161
  if (typeof runtime !== 'string' || !runtimeComPerfil(runtime))
148
162
  throw new locale_1.ErroDoCatalogo((m) => m.accounts.runtimeDesconhecido);
149
- const campo = runtime === 'claude-bg' ? 'configDir' : 'codexHome';
150
- const outro = runtime === 'claude-bg' ? 'codexHome' : 'configDir';
151
- if (o[outro] !== undefined || Object.keys(o).some(k => !['id', 'runtime', campo].includes(k)))
163
+ const campo = exports.CAMPO_DO_PERFIL[runtime];
164
+ if (Object.keys(o).some(k => !['id', 'runtime', campo].includes(k)))
152
165
  throw new locale_1.ErroDoCatalogo((m) => m.accounts.despachoComCampo);
153
- const id = validarIdDePerfil(o.id), dir = validarDiretorioDePerfil(o[campo]);
154
- return runtime === 'claude-bg' ? { id, runtime, configDir: dir } : { id, runtime, codexHome: dir };
166
+ return comDiretorio(validarIdDePerfil(o.id), runtime, validarDiretorioDePerfil(o[campo]));
155
167
  }
156
168
  function perfilDeDespacho(p) {
157
- return p.runtime === 'claude-bg' ? { id: p.id, runtime: p.runtime, configDir: diretorioDoPerfil(p) }
158
- : { id: p.id, runtime: p.runtime, codexHome: diretorioDoPerfil(p) };
169
+ return comDiretorio(p.id, p.runtime, diretorioDoPerfil(p));
159
170
  }
160
171
  /**
161
172
  * Diretorio implicito de quem despacha sem perfil: o do ambiente informado, ou o padrao do
@@ -163,7 +174,7 @@ function perfilDeDespacho(p) {
163
174
  */
164
175
  function diretorioImplicito(runtime, env = process.env) {
165
176
  const valor = (env[exports.VARIAVEL_DO_PERFIL[runtime]] ?? '').trim();
166
- return valor !== '' ? valor : path.join(os.homedir(), runtime === 'claude-bg' ? '.claude' : '.codex');
177
+ return valor !== '' ? valor : path.join(os.homedir(), PASTA_PADRAO[runtime]);
167
178
  }
168
179
  /** Diretorio efetivo: o do perfil registrado, ou o implicito quando a sessao nao tem perfil. */
169
180
  function diretorioEfetivo(runtime, perfil, env = process.env) {
@@ -247,8 +258,8 @@ function validarPerfil(v) {
247
258
  throw new locale_1.ErroDoCatalogo((m) => m.accounts.perfilInvalido);
248
259
  const o = v;
249
260
  const despacho = validarPerfilDeDespacho({ id: o.id, runtime: o.runtime,
250
- ...(o.configDir !== undefined ? { configDir: o.configDir } : {}), ...(o.codexHome !== undefined ? { codexHome: o.codexHome } : {}) });
251
- const permitidos = ['id', 'runtime', 'configDir', 'codexHome', 'estado', 'esgotadoAte', 'ultimaFalha', 'ultimoUso', 'criadoEm'];
261
+ ...Object.fromEntries(CAMPOS_DE_DIRETORIO.filter(c => o[c] !== undefined).map(c => [c, o[c]])) });
262
+ const permitidos = ['id', 'runtime', ...CAMPOS_DE_DIRETORIO, 'estado', 'esgotadoAte', 'ultimaFalha', 'ultimoUso', 'criadoEm'];
252
263
  if (Object.keys(o).some(k => !permitidos.includes(k)))
253
264
  throw new locale_1.ErroDoCatalogo((m) => m.accounts.campoInesperado);
254
265
  if (!exports.ESTADOS_DE_PERFIL.includes(o.estado))
@@ -539,8 +550,7 @@ function adicionarPerfil(raiz, novo, agoraIso = new Date().toISOString()) {
539
550
  throw new locale_1.ErroDoCatalogo((m) => m.accounts.runtimeSemPerfil(novo.runtime));
540
551
  const runtime = novo.runtime;
541
552
  const dir = validarDiretorioDePerfil(novo.dir);
542
- const perfil = { id: validarIdDePerfil(novo.id), runtime,
543
- ...(runtime === 'claude-bg' ? { configDir: dir } : { codexHome: dir }),
553
+ const perfil = { ...comDiretorio(validarIdDePerfil(novo.id), runtime, dir),
544
554
  estado: 'ativo', esgotadoAte: null, ultimaFalha: null, ultimoUso: null, criadoEm: agoraIso };
545
555
  return atualizarPerfis(raiz, store => {
546
556
  const existente = store.perfis.find(p => p.id === perfil.id);
package/dist/runtimes.js CHANGED
@@ -10,6 +10,10 @@
10
10
  * `claude agents --json` (homologado em 02/09/2026).
11
11
  * codex o segundo runtime: `codex exec` headless desanexado, sessao verificada no
12
12
  * rollout que o proprio Codex grava (homologado em 06/09/2026).
13
+ * grok RM-067 (F1 e F2): o Grok Build headless, `grok -p` com `streaming-json`, sessao
14
+ * verificada na pasta que o proprio runtime grava. Desde a F4, runtime de bloco e de
15
+ * fallback no `ork setup`, sempre com o modelo dado pelo dono; nunca o padrao do projeto
16
+ * (`runtime.adapter`), que segue com o claude-bg e o codex.
13
17
  *
14
18
  * Paridade sem downgrade: o contrato do claude-bg nao muda com a chegada do codex, e todo
15
19
  * runtime novo entra por aqui com o MESMO contrato de despacho (dry-run, rate limit
@@ -49,11 +53,14 @@ var __importStar = (this && this.__importStar) || (function () {
49
53
  };
50
54
  })();
51
55
  Object.defineProperty(exports, "__esModule", { value: true });
52
- exports.ORDEM_DOS_RUNTIMES = exports.RUNTIMES = exports.RUNTIME_PADRAO = void 0;
56
+ exports.RUNTIMES_SEM_COTA_TIPADA = exports.RUNTIMES_DE_BLOCO = exports.ORDEM_DOS_RUNTIMES = exports.RUNTIMES = exports.RUNTIME_PADRAO = void 0;
53
57
  exports.runtimeConhecido = runtimeConhecido;
58
+ exports.runtimeDeBloco = runtimeDeBloco;
59
+ exports.runtimeDoManifesto = runtimeDoManifesto;
54
60
  exports.resolverRuntime = resolverRuntime;
55
61
  const claudeBg = __importStar(require("./adapters/claude-bg"));
56
62
  const codex = __importStar(require("./adapters/codex"));
63
+ const grok = __importStar(require("./adapters/grok"));
57
64
  const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
58
65
  const locale_1 = require("./locale");
59
66
  exports.RUNTIME_PADRAO = 'claude-bg';
@@ -87,18 +94,48 @@ exports.RUNTIMES = {
87
94
  montarComando: (p) => codex.montarComando(p),
88
95
  despachar: (p) => comRecibosPublicos(p, codex.despachar),
89
96
  },
97
+ grok: {
98
+ nome: grok.NOME_ADAPTER,
99
+ fonteVerificacao: grok.FONTE_VERIFICACAO,
100
+ disponivel: () => grok.disponivel(),
101
+ versao: grok.versao,
102
+ montarComando: (p) => grok.montarComando(p),
103
+ despachar: (p) => comRecibosPublicos(p, grok.despachar),
104
+ },
90
105
  };
91
- /** Os runtimes na ordem de maturidade (o padrao primeiro). */
106
+ /**
107
+ * Os runtimes que podem ser o padrao do projeto (`runtime.adapter` do manifesto), na ordem de maturidade (o padrao
108
+ * primeiro). RM-067 (F4): o grok fica fora, porque fazer dele o runtime padrao esta fora do escopo da RM-067 e ele nao tem
109
+ * modelo padrao.
110
+ */
92
111
  exports.ORDEM_DOS_RUNTIMES = ['claude-bg', 'codex'];
93
- /** O nome e de um runtime homologado? */
112
+ /**
113
+ * Os runtimes que o setup aceita como runtime ou fallback de bloco. RM-067 (F4): os do padrao e o grok, este sempre com o
114
+ * modelo dado pelo dono (o setup exige `--model` ao trocar o runtime, e a entrada de fallback e `runtime:modelo[:esforco]`).
115
+ */
116
+ exports.RUNTIMES_DE_BLOCO = [...exports.ORDEM_DOS_RUNTIMES, 'grok'];
117
+ /**
118
+ * RM-067 (F3): runtimes cuja cota esgotada ainda nao vem tipada do proprio runtime. A escada automatica nunca troca a conta
119
+ * deles: sem o sinal tipado, nada prova que o perfil esgotou. O grok sai daqui quando a F5 ler o formato estruturado.
120
+ */
121
+ exports.RUNTIMES_SEM_COTA_TIPADA = ['grok'];
122
+ /** O nome e de um runtime homologado (resolve por nome no despacho)? */
94
123
  function runtimeConhecido(nome) {
95
124
  return Object.prototype.hasOwnProperty.call(exports.RUNTIMES, nome);
96
125
  }
126
+ /** O runtime pode ser runtime de bloco ou fallback no setup? */
127
+ function runtimeDeBloco(nome) {
128
+ return exports.RUNTIMES_DE_BLOCO.includes(nome);
129
+ }
130
+ /** O runtime pode ser o `runtime.adapter` do manifesto? */
131
+ function runtimeDoManifesto(nome) {
132
+ return exports.ORDEM_DOS_RUNTIMES.includes(nome);
133
+ }
97
134
  /** Resolve o adapter pelo nome, com erro tipado quando o runtime nao existe. */
98
135
  function resolverRuntime(nome) {
99
- const adapter = exports.RUNTIMES[nome];
136
+ const adapter = runtimeConhecido(nome) ? exports.RUNTIMES[nome] : undefined;
100
137
  if (!adapter) {
101
- throw new locale_1.ErroDoCatalogo((m) => m.phase.runtimeDesconhecido(nome, exports.ORDEM_DOS_RUNTIMES.join(', ')));
138
+ throw new locale_1.ErroDoCatalogo((m) => m.phase.runtimeDesconhecido(nome, exports.RUNTIMES_DE_BLOCO.join(', ')));
102
139
  }
103
140
  return adapter;
104
141
  }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.PRAZO_MINIMO_DO_CLASSIC_MS = exports.PRAZO_DO_BUILD_DA_IMAGEM_MS = exports.PRAZO_POR_REGRA_DO_SEMGREP_S = exports.SEMGREP_SEM_CACHE = void 0;
37
37
  exports.especificacaoDoAgile = especificacaoDoAgile;
38
+ exports.prazoDoBuildDaImagem = prazoDoBuildDaImagem;
38
39
  exports.estourouNoAgile = estourouNoAgile;
39
40
  exports.motivoSemCredencial = motivoSemCredencial;
40
41
  exports.resultadoDoTrabalhador = resultadoDoTrabalhador;
@@ -69,7 +70,6 @@ const tools_1 = require("./tools");
69
70
  const TRABALHADOR = path.join(__dirname, 'trabalhador.js');
70
71
  /** Sem prazo da rodada, o trabalhador ganha o maior prazo do catalogo para a HEAD e para a base, com folga. */
71
72
  const FOLGA_MS = 60_000;
72
- const aspas = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
73
73
  /**
74
74
  * O Semgrep baixa as regras do registro (`p/typescript`, ...) a cada rodada, e nao tem cache delas. Regra lida de arquivo
75
75
  * local ganha outro `check_id` (o prefixo do caminho), o que muda a impressao digital de todo achado: o cache de regras
@@ -131,18 +131,24 @@ function eslintDoAgile(t, prazoMs, pecas) {
131
131
  if (t.name !== 'eslint' || !t.install?.length)
132
132
  return t;
133
133
  // A4 do CHECK: o build roda antes dos trabalhadores; com teto curto, um link lento nao consome o prazo da varredura toda.
134
- const imagem = (0, imagem_de_lint_1.garantirImagemDeLint)(t, Math.min(prazoMs, exports.PRAZO_DO_BUILD_DA_IMAGEM_MS));
134
+ const imagem = (0, imagem_de_lint_1.garantirImagemDeLint)(t, prazoDoBuildDaImagem(prazoMs));
135
135
  const comImagem = 'tag' in imagem ? (0, imagem_de_lint_1.eslintComImagem)(t, imagem.tag) : null;
136
136
  if (comImagem) {
137
137
  pecas.push({ peca: 'imagem-de-lint', estado: 'agile' });
138
138
  return comImagem;
139
139
  }
140
- pecas.push({ peca: 'imagem-de-lint', estado: 'fallback', motivo: 'motivo' in imagem ? imagem.motivo : 'script do catalogo sem o npm install esperado',
140
+ // RM-074 (F4): a ultima linha do `docker build` vem do registro do npm e pode trazer login na URL; vai ao ledger redigida.
141
+ pecas.push({ peca: 'imagem-de-lint', estado: 'fallback',
142
+ motivo: 'motivo' in imagem ? motivoSemCredencial(imagem.motivo, 200) : 'script do catalogo sem o npm install esperado',
141
143
  fallback: 'eslint do catalogo, com npm install no container' });
142
144
  return t;
143
145
  }
144
146
  /** O teto do build da imagem de lint (A4 do CHECK): estourou, o `eslint` do catalogo, com a peca em fallback. */
145
147
  exports.PRAZO_DO_BUILD_DA_IMAGEM_MS = 180_000;
148
+ /** O prazo do build da imagem de lint: o que resta da ferramenta, nunca acima do teto (RM-074 F4: o caso do teto). */
149
+ function prazoDoBuildDaImagem(restanteMs) {
150
+ return Math.min(restanteMs, exports.PRAZO_DO_BUILD_DA_IMAGEM_MS);
151
+ }
146
152
  /** O prazo minimo que sobra para a ferramenta que caiu no Agile rodar pelo caminho do Classic (A5 do CHECK). */
147
153
  exports.PRAZO_MINIMO_DO_CLASSIC_MS = 30_000;
148
154
  /** Os motivos tipados de estouro que o `scanTool` grava: o prazo da HEAD e o da base. */
@@ -228,8 +234,11 @@ function catalogoDoAgile(catalogo, agile, pecas) {
228
234
  pecas.push({ peca: 'oxlint', estado: 'agile', motivo: 'primeiro passe antes do eslint, sem tirar regra dele (check.agile.oxlint)' });
229
235
  return lista;
230
236
  }
231
- /** A rodada do Agile: as mesmas regras do `runScan`, com as ferramentas em paralelo e os caches de banco. */
232
- function runScanAgil(opts) {
237
+ /**
238
+ * A rodada do Agile: as mesmas regras do `runScan`, com as ferramentas em paralelo e os caches de banco. `rodar` e o
239
+ * grupo paralelo; os testes o trocam para ler o prazo e o argv de cada trabalhador (RM-074 F4).
240
+ */
241
+ function runScanAgil(opts, rodar = check_paralelo_1.rodarEmParalelo) {
233
242
  const start = path.resolve(opts.root);
234
243
  const root = (0, run_1.git)(start, ['rev-parse', '--show-toplevel']) ?? start;
235
244
  const generatedAt = (opts.now ?? (() => new Date()))().toISOString();
@@ -306,7 +315,9 @@ function runScanAgil(opts) {
306
315
  ctx: { headTree, mainRoot, base, head, rangeCommits: count === null ? null : Number(count), deadline, ...(baseTree ? { baseTree } : {}) } };
307
316
  const arquivo = path.join(dir, `${i}.json`);
308
317
  fs.writeFileSync(arquivo, JSON.stringify(trabalho), { mode: 0o600 });
309
- return { nome: t.name, comando: `${aspas(process.execPath)} ${aspas(TRABALHADOR)} ${aspas(arquivo)}`, cwd: root };
318
+ // RM-074 (F4): o trabalhador roda pelo argv, sem string de shell; o `comando` so identifica o item.
319
+ const argv = [process.execPath, TRABALHADOR, arquivo];
320
+ return { nome: t.name, comando: argv.join(' '), argv, cwd: root };
310
321
  });
311
322
  // S-f do CHECK 2: o teto dos trabalhadores e contado depois de todos montados, com o build da imagem de lint ja feito.
312
323
  const prazo = deadline === null ? Math.max(...tools.map(t => t.timeoutMs)) * 2 + FOLGA_MS : Math.max(1000, deadline - Date.now() + FOLGA_MS);
@@ -316,7 +327,7 @@ function runScanAgil(opts) {
316
327
  const maxParalelo = Number.isInteger(teto) && teto > 0 ? teto : undefined;
317
328
  if (maxParalelo)
318
329
  pecas[0] = { ...pecas[0], motivo: `ate ${maxParalelo} ferramenta(s) ao mesmo tempo, ao lado das lanes do verify` };
319
- const processos = (0, check_paralelo_1.rodarEmParalelo)(trabalhos, process.env, maxParalelo ? { maxParalelo } : {});
330
+ const processos = rodar(trabalhos, process.env, maxParalelo ? { maxParalelo } : {});
320
331
  const excluded = new Set();
321
332
  tools.forEach((t, i) => {
322
333
  const trabalhador = resultadoDoTrabalhador(t, processos[i], trabalhos[i].prazoMs);
@@ -55,7 +55,7 @@ function executarSecurityScan(argv, ctx) {
55
55
  const message = e.message;
56
56
  if (!/^security-scan\.(config-invalid|dismissal-invalid)/.test(message))
57
57
  throw e;
58
- console.error(message);
58
+ console.error(json ? message : m.error(message));
59
59
  return 1;
60
60
  }
61
61
  const files = (0, report_1.writeReport)(ctx.estado, report);
@@ -68,7 +68,7 @@ function executarSecurityScan(argv, ctx) {
68
68
  if (notRun.length) {
69
69
  ctx.escrever(m.notRunHeading);
70
70
  for (const r of notRun)
71
- ctx.escrever(m.notRun(r.name, m.states[r.state] ?? r.state, r.reason ?? ''));
71
+ ctx.escrever(m.notRun(r.name, m.states[r.state] ?? r.state, m.reason(r.reason ?? '')));
72
72
  }
73
73
  ctx.escrever(files.md);
74
74
  }
@@ -38,6 +38,7 @@ exports.defaultProbe = defaultProbe;
38
38
  exports.chooseExecutor = chooseExecutor;
39
39
  exports.nativeVersionOf = nativeVersionOf;
40
40
  exports.defaultsDir = defaultsDir;
41
+ exports.argumentosDeCpuDoDocker = argumentosDeCpuDoDocker;
41
42
  exports.expand = expand;
42
43
  exports.ensureImage = ensureImage;
43
44
  exports.runDocker = runDocker;
@@ -52,6 +53,7 @@ const crypto = __importStar(require("node:crypto"));
52
53
  const fs = __importStar(require("node:fs"));
53
54
  const path = __importStar(require("node:path"));
54
55
  const guard_1 = require("./guard");
56
+ const agendador_cpu_1 = require("../agendador-cpu");
55
57
  const PROBE_TIMEOUT_MS = 20000;
56
58
  /** The probe of this machine, through the PATH. Each answer is computed once per probe. */
57
59
  function defaultProbe() {
@@ -107,6 +109,11 @@ function defaultsDir() {
107
109
  exports.CONFIG_FILES = { gitleaks: 'gitleaks.toml', eslint: 'eslint.config.mjs', ruff: 'ruff.toml', 'golangci-lint': '.golangci.yml' };
108
110
  const CONTAINER = { src: '/src', out: '/out', config: '/config' };
109
111
  const MAX_BUFFER = 512 * 1024 * 1024;
112
+ /** A cota so existe no grupo Agile; sem a variavel, o Classic continua com o argv de sempre. */
113
+ function argumentosDeCpuDoDocker(ambiente = process.env) {
114
+ const limite = Number(ambiente[agendador_cpu_1.ENV_CPUS_DOCKER]);
115
+ return Number.isFinite(limite) && limite > 0 ? ['--cpus', String(limite)] : [];
116
+ }
110
117
  function expand(arg, values) {
111
118
  return arg.replace(/\{(src|out|base|config|install|stackDir)\}/g, (_, k) => values[k]);
112
119
  }
@@ -141,6 +148,7 @@ function runDocker(inv) {
141
148
  fs.chmodSync(inv.outHost, 0o777);
142
149
  const values = { src: CONTAINER.src, out: CONTAINER.out, base: inv.base, config: configCtr, install: (tool.install ?? []).join(' '), stackDir: stackDirArg(inv.stackDir) };
143
150
  const args = ['run', '--rm', '--name', name, '--pull', 'never', '--user', containerUser(),
151
+ ...argumentosDeCpuDoDocker(),
144
152
  '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges',
145
153
  ...(tool.network === 'none' ? ['--network', 'none'] : []),
146
154
  '--tmpfs', '/tmp:exec,mode=1777',