@orkastery/cli 0.5.0 → 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.
- package/adapters/claude-code/README.md +11 -2
- package/adapters/claude-code/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/onboarding.md +3 -2
- package/adapters/claude-code/commands/ork.md +9 -0
- package/adapters/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
- package/adapters/openclaw/README.md +35 -4
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +47 -12
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +50 -12
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +59 -11
- package/dist/adapters/codex.js +4 -3
- package/dist/agents-md.js +1 -0
- package/dist/auditrun.js +6 -2
- package/dist/board.js +16 -14
- package/dist/branch-de-estado.js +66 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/ci.js +8 -1
- package/dist/claim-lint.js +3 -1
- package/dist/claims.js +39 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/docs.js +110 -3
- package/dist/doctor-pulse-cron.js +194 -0
- package/dist/doctor-rede.js +149 -0
- package/dist/doctor.js +294 -37
- package/dist/entrega-pr.js +27 -8
- package/dist/esgotamentos.js +181 -0
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-estado.js +35 -9
- package/dist/fabrica-publicar.js +6 -0
- package/dist/fechamento.js +20 -66
- package/dist/fix.js +3 -3
- package/dist/forja.js +31 -7
- package/dist/gates.js +5 -1
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +33 -1
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/horario.js +27 -0
- package/dist/hosts.js +59 -13
- package/dist/impedimento.js +205 -0
- package/dist/index.js +433 -88
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +3 -1
- package/dist/init.js +28 -5
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +267 -30
- package/dist/intelligence-graph-contexto.js +215 -0
- package/dist/intelligence-graph-contract.js +71 -12
- package/dist/intelligence-graph-extract-md.js +41 -23
- package/dist/intelligence-graph-extract-ts.js +181 -11
- package/dist/intelligence-graph-extract.js +186 -9
- package/dist/intelligence-graph-index.js +205 -24
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/intelligence-graph-repo.js +10 -0
- package/dist/leases.js +680 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +12 -0
- package/dist/licoes.js +6 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-runtime.js +7 -1
- package/dist/manifest.js +44 -1
- package/dist/master-nota.js +14 -5
- package/dist/master.js +129 -12
- package/dist/mcp-artifacts.js +36 -3
- package/dist/mcp-git.js +80 -15
- package/dist/mcp-grafo-worker.js +93 -0
- package/dist/mcp-grafo.js +279 -0
- package/dist/mcp-install.js +2 -1
- package/dist/mcp-server.js +23 -1
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/memoria.js +48 -6
- package/dist/network-roadmap.js +195 -20
- package/dist/ocupacao.js +17 -5
- package/dist/onboarding.js +9 -4
- package/dist/orkmind.js +90 -5
- package/dist/orquestracao.js +14 -11
- package/dist/parado-no-condutor.js +1089 -0
- package/dist/phase.js +68 -15
- package/dist/policies.js +79 -3
- package/dist/portfolio.js +4 -1
- package/dist/projeto-alvo.js +5 -1
- package/dist/prompts.js +36 -1
- package/dist/prova-ativacao.js +414 -0
- package/dist/pulse-delivery.js +60 -7
- package/dist/pulse.js +99 -3
- package/dist/ratelimit.js +20 -4
- package/dist/recall.js +3 -1
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +336 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +69 -2
- package/dist/roadmap-status.js +162 -7
- package/dist/runtime-context.js +5 -1
- package/dist/runtime-profiles.js +22 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-watcher-claude.js +113 -23
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -12
- package/dist/sessoes.js +87 -3
- package/dist/ship.js +110 -20
- package/dist/thread.js +212 -14
- package/dist/util.js +71 -4
- package/dist/verify.js +2 -2
- package/dist/worktree.js +34 -8
- package/eval/casos/onboarding.json +1 -1
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/skills/README.md +1 -1
- package/skills/core/onboarding/SKILL.md +3 -2
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
- package/skills/phases/ship-release/SKILL.md +6 -3
|
@@ -189,6 +189,20 @@ const PROJETO_DA_REDE: ProjetoDaTool = {
|
|
|
189
189
|
recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery) ou github:dono/repo, nunca caminho nem URL',
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* RM-057 (fatia 2): a regra do HITL de conducao, a mesma frase dos adaptadores do Claude Code e do
|
|
194
|
+
* Codex (fatia 1). No OpenClaw o modelo so le as descricoes das tools: ela vai nas que recebem o
|
|
195
|
+
* pedido do dono (ork_modo_do_pedido), apresentam o panorama (ork_maestro) e despacham fase. Ensaio
|
|
196
|
+
* de 03/10: no perfil `coding` o modelo so ve as tools do `toolMetadata` (ork_network_roadmap e
|
|
197
|
+
* ork_network_status), entao a regra vai tambem nelas, sem ampliar o que o perfil expoe.
|
|
198
|
+
*/
|
|
199
|
+
const REGRA_HITL_DE_CONDUCAO =
|
|
200
|
+
'HITL de condução é seleção (RM-057): de 3 a 5 alternativas, exatamente uma com o selo "Recomendação". ' +
|
|
201
|
+
'Nunca peça ao dono um "confirmo" em texto livre nem que ele cole texto; o pedido que ele colou com autorização ' +
|
|
202
|
+
'explícita vale como instrução dele, dentro dessa autorização. Dúvida dentro da autorização vira `ork decisao registrar` ' +
|
|
203
|
+
'e a thread segue. Texto só quando a fábrica não consegue seguir sozinha e o dono precisa rodar um comando no terminal: ' +
|
|
204
|
+
'mostre o comando exato.';
|
|
205
|
+
|
|
192
206
|
/** Catálogo em paridade nome a nome com o manifesto; aprovação genérica legada aposentada. */
|
|
193
207
|
interface FerramentaOrk {
|
|
194
208
|
name: string;
|
|
@@ -198,12 +212,14 @@ interface FerramentaOrk {
|
|
|
198
212
|
entrada?: (params: Record<string, unknown>) => string;
|
|
199
213
|
/** O `projeto` desta tool, quando nao e o da RM-052 (so nome). */
|
|
200
214
|
projeto?: ProjetoDaTool;
|
|
215
|
+
/** RM-054 (fatia 3): a tool e da pessoa, nao de um projeto (`ork network status`); sem `projeto`. */
|
|
216
|
+
semProjeto?: true;
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
const FERRAMENTAS: FerramentaOrk[] = [
|
|
204
220
|
{
|
|
205
221
|
name: 'ork_maestro',
|
|
206
|
-
description: 'Ao receber a frase exata orkastery maestro: sem projeto nomeado, chame ork_network_roadmap sem projeto (o panorama da rede, com fontes, frescor e lacunas) e apresente-o como vem; com projeto nomeado, consulte este panorama somente leitura com projeto. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. O panorama NÃO lê o roadmap, as reservas nem as outras máquinas (veja notConsulted): zero threads nunca é roadmap vazio; para o roadmap use ork_network_roadmap. Ação posterior exige operação autorizada e readback.',
|
|
222
|
+
description: 'Ao receber a frase exata orkastery maestro: sem projeto nomeado, chame ork_network_roadmap sem projeto (o panorama da rede, com fontes, frescor e lacunas) e apresente-o como vem; com projeto nomeado, consulte este panorama somente leitura com projeto. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. O panorama NÃO lê o roadmap, as reservas nem as outras máquinas (veja notConsulted): zero threads nunca é roadmap vazio; para o roadmap use ork_network_roadmap. Ação posterior exige operação autorizada e readback. ' + REGRA_HITL_DE_CONDUCAO,
|
|
207
223
|
parameters: { type: 'object', additionalProperties: false, properties: {
|
|
208
224
|
thread: { type: 'string' }, section: { type: 'string', enum: ['portfolio','demands','threads','sessions','blockers','leases','retries','hitl','ship','master','nextActions'] },
|
|
209
225
|
offset: { type: 'integer', minimum: 0, maximum: 100000 },
|
|
@@ -220,7 +236,7 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
220
236
|
},
|
|
221
237
|
{
|
|
222
238
|
name: 'ork_onboarding',
|
|
223
|
-
description: 'Consulta a pauta do núcleo e registra respostas públicas. Credenciais somente como nomes de variáveis; valores ficam
|
|
239
|
+
description: 'Consulta a pauta do núcleo e registra respostas públicas. Credenciais somente como nomes de variáveis; valores ficam no ambiente do processo ou no cofre do host (no Hermes, ~/.hermes/.env). Sync publica opcionalmente e relata degradação.',
|
|
224
240
|
parameters: {
|
|
225
241
|
type: 'object', additionalProperties: false, required: ['acao'],
|
|
226
242
|
properties: {
|
|
@@ -245,7 +261,7 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
245
261
|
{
|
|
246
262
|
name: 'ork_modo_do_pedido',
|
|
247
263
|
description:
|
|
248
|
-
'Le a #TAG de conducao do pedido do builder (#Classic, #Maestro, #Auto, #Fast) e devolve o modo. #TAG aposentada (#Look, #Ork) recebe recusa tipada modo.aposentado, com saida != 0, em vez de virar o default em silencio. Sem tag, devolve o conduction.default_mode do manifesto. Use SEMPRE este tool em vez de reconhecer a tag no host.',
|
|
264
|
+
'Le a #TAG de conducao do pedido do builder (#Classic, #Maestro, #Auto, #Fast) e devolve o modo. #TAG aposentada (#Look, #Ork) recebe recusa tipada modo.aposentado, com saida != 0, em vez de virar o default em silencio. Sem tag, devolve o conduction.default_mode do manifesto. Use SEMPRE este tool em vez de reconhecer a tag no host. ' + REGRA_HITL_DE_CONDUCAO,
|
|
249
265
|
parameters: schema({
|
|
250
266
|
pedido: { type: 'string', description: 'O texto inteiro do pedido do builder' },
|
|
251
267
|
}),
|
|
@@ -281,12 +297,18 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
281
297
|
{
|
|
282
298
|
name: 'ork_phase_run',
|
|
283
299
|
description:
|
|
284
|
-
'Despacha uma fase (GOAL PLAN GO CHECK SHIP MASTER) pelo runtime adapter, gravando o prompt exato com sha256 no ledger.'
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
300
|
+
'Despacha uma fase (GOAL PLAN GO CHECK SHIP MASTER) pelo runtime adapter, gravando o prompt exato com sha256 no ledger. ' +
|
|
301
|
+
REGRA_HITL_DE_CONDUCAO,
|
|
302
|
+
parameters: {
|
|
303
|
+
type: 'object', additionalProperties: false, required: ['thread', 'fase', 'prompt'],
|
|
304
|
+
properties: {
|
|
305
|
+
thread: { type: 'string' },
|
|
306
|
+
fase: { type: 'string', enum: ['GOAL', 'PLAN', 'GO', 'CHECK', 'SHIP', 'MASTER'] },
|
|
307
|
+
prompt: { type: 'string', description: 'O pedido do builder para esta fase' },
|
|
308
|
+
// RM-056: a conta do despacho; o nucleo recusa perfil inexistente, de outro runtime, esgotado ou sem login.
|
|
309
|
+
perfil: { type: 'string', description: 'id do perfil de conta (ork accounts list); omita para o rodizio do nucleo' },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
290
312
|
argv: (p) => [
|
|
291
313
|
'phase',
|
|
292
314
|
'run',
|
|
@@ -294,6 +316,7 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
294
316
|
texto(p, 'fase'),
|
|
295
317
|
'--prompt',
|
|
296
318
|
texto(p, 'prompt'),
|
|
319
|
+
...(p.perfil === undefined ? [] : ['--perfil', texto(p, 'perfil')]),
|
|
297
320
|
],
|
|
298
321
|
},
|
|
299
322
|
{
|
|
@@ -458,12 +481,21 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
458
481
|
{
|
|
459
482
|
name: 'ork_network_roadmap',
|
|
460
483
|
description:
|
|
461
|
-
'Roadmap da rede, somente leitura: para cada projeto, o status report do roadmap (RM-048) com as threads de TODAS as máquinas, as reservas, as threads por máquina com a idade da batida, a fonte e a hora de cada parte e as lacunas. É a fonte para qualquer pergunta sobre o roadmap ou o status report: transporte o texto como vem, sem reescrever nem resumir. Passe projeto com o nome que o dono pediu (ex.: orkastery) ou github:dono/repo; sem projeto, vêm todos os projetos conhecidos, e esse é o panorama da frase orkastery maestro sem projeto. Lacuna, "não lido" e "Não consultado" são fontes que ficaram sem leitura: nunca conclua "roadmap vazio" nem "nenhuma máquina publicou" a partir delas.',
|
|
484
|
+
'Roadmap da rede, somente leitura: para cada projeto, o status report do roadmap (RM-048) com as threads de TODAS as máquinas, as reservas, as threads por máquina com a idade da batida, a fonte e a hora de cada parte e as lacunas. É a fonte para qualquer pergunta sobre o roadmap ou o status report: transporte o texto como vem, sem reescrever nem resumir. Passe projeto com o nome que o dono pediu (ex.: orkastery) ou github:dono/repo; sem projeto, vêm todos os projetos conhecidos, e esse é o panorama da frase orkastery maestro sem projeto. Lacuna, "não lido" e "Não consultado" são fontes que ficaram sem leitura: nunca conclua "roadmap vazio" nem "nenhuma máquina publicou" a partir delas. ' + REGRA_HITL_DE_CONDUCAO,
|
|
462
485
|
parameters: schema({}),
|
|
463
486
|
projeto: PROJETO_DA_REDE,
|
|
464
487
|
// RM-054 (fatia 2): uma chamada de CLI; o `--projeto` vai no inicio e o nucleo o devolve ao `network`.
|
|
465
488
|
argv: () => ['network', 'roadmap'],
|
|
466
489
|
},
|
|
490
|
+
{
|
|
491
|
+
name: 'ork_network_status',
|
|
492
|
+
description:
|
|
493
|
+
'Status da Orkastery Network, somente leitura: as máquinas da pessoa (a casa privada na forja), com a batida de cada uma, a forja e o login, os runtimes e hosts com versão e os projetos que cada uma declara, mais a fonte e as lacunas. É a fonte para "quais máquinas eu tenho" e "qual máquina está parada": transporte o texto como vem. Lacuna e "Não consultado" são o que não foi lido: nunca conclua "nenhuma máquina" nem "rede vazia" a partir delas. Não lê roadmap nem threads: para isso, ork_network_roadmap. ' + REGRA_HITL_DE_CONDUCAO,
|
|
494
|
+
parameters: schema({}),
|
|
495
|
+
semProjeto: true,
|
|
496
|
+
// RM-054 (fatia 3): uma chamada de CLI; a rede e da pessoa e nao recebe `--projeto`.
|
|
497
|
+
argv: () => ['network', 'status'],
|
|
498
|
+
},
|
|
467
499
|
{
|
|
468
500
|
name: 'ork_master_batch',
|
|
469
501
|
description:
|
|
@@ -475,6 +507,7 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
475
507
|
|
|
476
508
|
/** RM-052: toda tool aceita `projeto` opcional, sem mudar o que ela ja exigia. */
|
|
477
509
|
function comProjeto(f: FerramentaOrk): Record<string, unknown> {
|
|
510
|
+
if (f.semProjeto) return f.parameters;
|
|
478
511
|
const propriedades = (f.parameters.properties ?? {}) as Record<string, unknown>;
|
|
479
512
|
const p = f.projeto;
|
|
480
513
|
const projeto = p ? { type: 'string', pattern: p.padrao.source, description: p.descricao } : PARAMETRO_PROJETO;
|
|
@@ -484,6 +517,11 @@ function comProjeto(f: FerramentaOrk): Record<string, unknown> {
|
|
|
484
517
|
/** `--projeto <nome>` vai no inicio do argv; o resto dos parametros segue para a tool como antes. */
|
|
485
518
|
function argvComProjeto(f: FerramentaOrk, params: Record<string, unknown>): { argv: string[]; resto: Record<string, unknown> } {
|
|
486
519
|
const { projeto, ...resto } = params;
|
|
520
|
+
if (f.semProjeto) {
|
|
521
|
+
// A tool sem `projeto` recusa o parametro em vez de ignora-lo: o modelo saberia que a rede veio filtrada.
|
|
522
|
+
if (projeto !== undefined) throw new Error('projeto.invalido');
|
|
523
|
+
return { argv: f.argv(resto), resto };
|
|
524
|
+
}
|
|
487
525
|
if (projeto !== undefined && (typeof projeto !== 'string' || !(f.projeto ?? PROJETO_DA_RM052).padrao.test(projeto))) {
|
|
488
526
|
throw new Error('projeto.invalido');
|
|
489
527
|
}
|
|
@@ -494,7 +532,7 @@ const plugin = defineToolPlugin({
|
|
|
494
532
|
id: 'orkastery',
|
|
495
533
|
name: 'Orkastery',
|
|
496
534
|
description:
|
|
497
|
-
'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap.',
|
|
535
|
+
'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool de projeto aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap; as maquinas da pessoa, de ork_network_status (sem projeto).',
|
|
498
536
|
tools: (tool) =>
|
|
499
537
|
FERRAMENTAS.map((f) =>
|
|
500
538
|
tool({
|
|
@@ -506,7 +544,7 @@ const plugin = defineToolPlugin({
|
|
|
506
544
|
try { chamada = argvComProjeto(f, params); }
|
|
507
545
|
catch (e) {
|
|
508
546
|
if ((e as Error).message === 'projeto.invalido') {
|
|
509
|
-
return `[ork recusou] projeto.invalido: ${(f.projeto ?? PROJETO_DA_RM052).recusa}`;
|
|
547
|
+
return `[ork recusou] projeto.invalido: ${f.semProjeto ? 'esta tool é da rede da pessoa e não recebe projeto; chame sem projeto' : (f.projeto ?? PROJETO_DA_RM052).recusa}`;
|
|
510
548
|
}
|
|
511
549
|
return '[ork recusou] resposta humana não confirmada; confira origem e correlação do pedido';
|
|
512
550
|
}
|
package/assets/orkmind_bridge.py
CHANGED
|
@@ -10,6 +10,7 @@ import math
|
|
|
10
10
|
import os
|
|
11
11
|
import re
|
|
12
12
|
import sys
|
|
13
|
+
from datetime import datetime, timezone
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class LegacyProvenanceCollision(ValueError):
|
|
@@ -370,6 +371,102 @@ async def health(store):
|
|
|
370
371
|
ORK_COLLECTIONS = ('decision', 'handoff', 'rule', 'learning', 'roadmap')
|
|
371
372
|
|
|
372
373
|
|
|
374
|
+
def no_universo(entry, tenant, agora=None):
|
|
375
|
+
"""RM-038: o predicado unico do universo da busca (indice, vetor, FTS e status).
|
|
376
|
+
|
|
377
|
+
O search_by_tags governado ja tira expiradas e injection_risk; o search_by_text nao tira
|
|
378
|
+
injection_risk. O mesmo predicado vale nos dois caminhos, com o tenant conferido como item
|
|
379
|
+
de lista: texto nunca casa por substring. `agora` e o instante de ANTES da leitura: o que a
|
|
380
|
+
biblioteca devolveu como ativo continua ativo aqui, sem corrida de expiracao.
|
|
381
|
+
"""
|
|
382
|
+
tags = getattr(entry, 'tags', None)
|
|
383
|
+
projeto = tags.get('project') if isinstance(tags, dict) else None
|
|
384
|
+
expira = getattr(entry, 'expires_at', None)
|
|
385
|
+
if expira is not None and expira.tzinfo is None:
|
|
386
|
+
expira = expira.replace(tzinfo=timezone.utc)
|
|
387
|
+
return (getattr(entry, 'collection', None) in ORK_COLLECTIONS
|
|
388
|
+
and isinstance(projeto, list) and tenant in projeto
|
|
389
|
+
and not getattr(entry, 'injection_risk', False)
|
|
390
|
+
and (expira is None or expira > (agora or datetime.now(timezone.utc))))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
async def ate_o_fim(contar, ler):
|
|
394
|
+
"""RM-038: le com a janela da contagem mais um; janela cheia conta e le de novo uma vez.
|
|
395
|
+
|
|
396
|
+
Cheia so acontece com escrita concorrente entre a contagem e a leitura. Uma escrita isolada
|
|
397
|
+
se resolve na segunda leitura; cheia de novo, a resposta e falhar alto, nunca cortar.
|
|
398
|
+
"""
|
|
399
|
+
for _ in range(2):
|
|
400
|
+
limite = await contar() + 1
|
|
401
|
+
lidas = await ler(limite)
|
|
402
|
+
if len(lidas) < limite:
|
|
403
|
+
return lidas
|
|
404
|
+
raise QueryError('memory.query.window-saturated')
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def universo_contract(request):
|
|
408
|
+
valid = isinstance(request, dict) and set(request) == {'op', 'tenant'}
|
|
409
|
+
tenant = request.get('tenant') if valid else None
|
|
410
|
+
if (not valid or request['op'] != 'universo' or not isinstance(tenant, str) or not tenant.strip()
|
|
411
|
+
or len(tenant) > 128 or re.search(r'[\x00-\x1f\x7f]', tenant) is not None):
|
|
412
|
+
raise QueryError('memory.universo.invalid')
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# Contagem SO de numeros: nenhuma coluna de conteudo sai da base por aqui. O tenant e o mesmo
|
|
416
|
+
# predicado da leitura governada por tag (`tags @> {"project": [tenant]}`), que usa o indice de tags.
|
|
417
|
+
FORA_DA_BUSCA_SQL = """
|
|
418
|
+
SELECT
|
|
419
|
+
count(*) FILTER (WHERE collection = ANY(%s) AND injection_risk
|
|
420
|
+
AND (expires_at IS NULL OR expires_at > now())) AS injecao,
|
|
421
|
+
count(*) FILTER (WHERE collection = ANY(%s) AND expires_at IS NOT NULL AND expires_at <= now()) AS expiradas,
|
|
422
|
+
count(*) FILTER (WHERE NOT (collection = ANY(%s))) AS outras_colecoes
|
|
423
|
+
FROM memories
|
|
424
|
+
WHERE tags @> jsonb_build_object('project', jsonb_build_array(%s::text))
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
async def contar_fora_da_busca(store, tenant):
|
|
429
|
+
"""RM-038: quantas entradas do tenant ficam fora do universo e por que.
|
|
430
|
+
|
|
431
|
+
A API governada nao devolve o que filtra; a contagem le a tabela pela conexao do adapter
|
|
432
|
+
pgvector (o mesmo acesso do lock de add e handoff), so com count(*). Em outro backend a
|
|
433
|
+
contagem nao e medida e volta None.
|
|
434
|
+
"""
|
|
435
|
+
if getattr(getattr(store, 'capabilities', None), 'backend', None) != 'pgvector':
|
|
436
|
+
return None
|
|
437
|
+
conn = await store.inner._get_conn()
|
|
438
|
+
colecoes = list(ORK_COLLECTIONS)
|
|
439
|
+
cur = await conn.execute(FORA_DA_BUSCA_SQL, (colecoes, colecoes, colecoes, tenant))
|
|
440
|
+
linha = await cur.fetchone()
|
|
441
|
+
return {'injecao': int(linha['injecao']), 'expiradas': int(linha['expiradas']),
|
|
442
|
+
'outrasColecoes': int(linha['outras_colecoes'])}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
async def universo(request, store):
|
|
446
|
+
"""RM-038: o universo da busca do tenant, lido ate o fim, uma leitura governada por colecao.
|
|
447
|
+
|
|
448
|
+
O tenant e filtro na origem: texto de outro tenant nunca sai da base por aqui. A janela e a
|
|
449
|
+
colecao inteira mais um (ate_o_fim). Entrada fora do predicado e violacao. O vetor gravado na
|
|
450
|
+
base nao viaja: o ork nao o usa.
|
|
451
|
+
"""
|
|
452
|
+
tenant = request['tenant']
|
|
453
|
+
agora = datetime.now(timezone.utc)
|
|
454
|
+
entradas = []
|
|
455
|
+
for colecao in ORK_COLLECTIONS:
|
|
456
|
+
async def contar(colecao=colecao):
|
|
457
|
+
return await store.count(colecao)
|
|
458
|
+
async def ler(limite, colecao=colecao):
|
|
459
|
+
return await store.search_by_tags({'project': [tenant]}, collection=colecao, limit=limite)
|
|
460
|
+
lidas = await ate_o_fim(contar, ler)
|
|
461
|
+
if any(e.collection != colecao or not no_universo(e, tenant, agora) for e in lidas):
|
|
462
|
+
raise QueryError('memory.query.scope-violation')
|
|
463
|
+
entradas.extend(lidas)
|
|
464
|
+
if len({e.id for e in entradas}) != len(entradas):
|
|
465
|
+
raise QueryError('memory.query.scope-violation')
|
|
466
|
+
return {'entradas': [e.model_dump(mode='json', exclude={'embedding'}) for e in entradas],
|
|
467
|
+
'foraDaBusca': await contar_fora_da_busca(store, tenant)}
|
|
468
|
+
|
|
469
|
+
|
|
373
470
|
def fts_contract(request):
|
|
374
471
|
valid = isinstance(request, dict) and set(request) == {'op', 'tenant', 'texto'}
|
|
375
472
|
def text(value, maximum):
|
|
@@ -379,14 +476,16 @@ def fts_contract(request):
|
|
|
379
476
|
|
|
380
477
|
|
|
381
478
|
async def fts(request, store):
|
|
382
|
-
"""FTS da biblioteca (I-38 D9)
|
|
479
|
+
"""FTS da biblioteca (I-38 D9) restrito ao universo da busca (RM-038): so ids, na ordem do ranking."""
|
|
383
480
|
# A janela cobre a base inteira: ausencia so conta quando nada ficou de fora do corte.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
481
|
+
agora = datetime.now(timezone.utc)
|
|
482
|
+
async def contar():
|
|
483
|
+
return await store.count()
|
|
484
|
+
async def ler(limite):
|
|
485
|
+
return await store.search_by_text(request['texto'], limit=limite)
|
|
486
|
+
entries = await ate_o_fim(contar, ler)
|
|
487
|
+
# O search_by_text alcanca injection_risk; o universo nao. Mesmo predicado do indice.
|
|
488
|
+
return {'ids': [e.id for e in entries if no_universo(e, request['tenant'], agora)]}
|
|
390
489
|
|
|
391
490
|
|
|
392
491
|
async def execute(request, store):
|
|
@@ -403,6 +502,9 @@ async def execute(request, store):
|
|
|
403
502
|
if op == 'fts':
|
|
404
503
|
fts_contract(request)
|
|
405
504
|
return await fts(request, store)
|
|
505
|
+
if op == 'universo':
|
|
506
|
+
universo_contract(request)
|
|
507
|
+
return await universo(request, store)
|
|
406
508
|
if op == 'query':
|
|
407
509
|
tags, collection, limit = query_contract(request)
|
|
408
510
|
# GovernedStore conserva validade, anti-injection e visibilidade existentes.
|
|
@@ -417,8 +519,12 @@ async def execute(request, store):
|
|
|
417
519
|
return [e.model_dump(mode='json') for e in entries]
|
|
418
520
|
if op == 'export':
|
|
419
521
|
collection = request['collection']
|
|
420
|
-
|
|
421
|
-
|
|
522
|
+
async def contar():
|
|
523
|
+
return await store.count(collection)
|
|
524
|
+
async def ler(limite):
|
|
525
|
+
return await store.search_by_tags({}, collection=collection, limit=limite)
|
|
526
|
+
# RM-038: le ate o fim; janela cheia de novo e falha alta, nunca corte em silencio.
|
|
527
|
+
entries = await ate_o_fim(contar, ler)
|
|
422
528
|
return [e.model_dump(mode='json') for e in entries]
|
|
423
529
|
if op == 'get':
|
|
424
530
|
entry = await store.retrieve(request['id'])
|
|
@@ -538,6 +644,8 @@ async def main(request):
|
|
|
538
644
|
health_contract(request)
|
|
539
645
|
if isinstance(request, dict) and request.get('op') == 'fts':
|
|
540
646
|
fts_contract(request)
|
|
647
|
+
if isinstance(request, dict) and request.get('op') == 'universo':
|
|
648
|
+
universo_contract(request)
|
|
541
649
|
native_schema_fields()
|
|
542
650
|
from orkmind.core.config import OrkMindConfig
|
|
543
651
|
from orkmind.store.factory import create_store
|
|
@@ -66,11 +66,13 @@ exports.parar = parar;
|
|
|
66
66
|
exports.pararAchada = pararAchada;
|
|
67
67
|
exports.acharSessaoNasContas = acharSessaoNasContas;
|
|
68
68
|
exports.ocupacaoDeContexto = ocupacaoDeContexto;
|
|
69
|
+
const horario_1 = require("../horario");
|
|
69
70
|
const util_1 = require("../util");
|
|
70
71
|
const runtime_ambiente_1 = require("../runtime-ambiente");
|
|
71
72
|
const path = __importStar(require("node:path"));
|
|
72
73
|
const runtime_context_1 = require("../runtime-context");
|
|
73
74
|
const runtime_profiles_1 = require("../runtime-profiles");
|
|
75
|
+
const mcp_grafo_1 = require("../mcp-grafo");
|
|
74
76
|
function exec(cmd, args, cwd, timeoutMs, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
75
77
|
return (0, util_1.exec)(cmd, args, cwd, timeoutMs, ambiente);
|
|
76
78
|
}
|
|
@@ -107,8 +109,14 @@ const MUTACOES_WORKTREE = [
|
|
|
107
109
|
'mcp__orkastery__ork_artifact_write', 'mcp__orkastery__ork_claim_add', 'mcp__orkastery__ork_decision_record',
|
|
108
110
|
'mcp__orkastery__ork_git_commit', 'mcp__orkastery__ork_verify', 'mcp__orkastery__ork_ship',
|
|
109
111
|
];
|
|
112
|
+
/**
|
|
113
|
+
* RM-031 KG5 (D7): as consultas do grafo, de leitura, so com a flag do manifesto da raiz; sem ela, nada muda.
|
|
114
|
+
* Montada na chamada: o `mcp-grafo` chega a este modulo pelo `mcp-git`, e no ciclo de imports o valor pode
|
|
115
|
+
* ainda nao existir quando este modulo carrega.
|
|
116
|
+
*/
|
|
117
|
+
const consultasDoGrafoMcp = () => mcp_grafo_1.TOOLS_DO_GRAFO.map((nome) => `mcp__orkastery__${nome}`);
|
|
110
118
|
/** Regras nativas de arquivo, nao sandbox de processos. Edit tambem cobre Write. */
|
|
111
|
-
function permissoesDaWorktree(cwd, permiteEditarProduto) {
|
|
119
|
+
function permissoesDaWorktree(cwd, permiteEditarProduto, consultas) {
|
|
112
120
|
if (!path.isAbsolute(cwd) || cwd === '/' || path.normalize(cwd) !== cwd || !/^[A-Za-z0-9_./-]+$/.test(cwd))
|
|
113
121
|
throw Error('runtime.context.permissions: worktree contem caracteres ambiguos para regras nativas; use caminho sem glob, espacos ou delimitadores');
|
|
114
122
|
const absoluto = '/' + cwd; // Claude exige // para caminho absoluto.
|
|
@@ -117,7 +125,7 @@ function permissoesDaWorktree(cwd, permiteEditarProduto) {
|
|
|
117
125
|
for (const prefixo of [`${absoluto}/${nome}`, `${absoluto}/**/${nome}`])
|
|
118
126
|
deny.push(`Edit(${prefixo})`, `Edit(${prefixo}/**)`);
|
|
119
127
|
}
|
|
120
|
-
return { allow: [...
|
|
128
|
+
return { allow: [...consultas, 'mcp__orkastery__ork_git_status', ...MUTACOES_WORKTREE, ...(permiteEditarProduto ? [`Edit(${absoluto}/**)`] : [])], deny };
|
|
121
129
|
}
|
|
122
130
|
/** O runtime esta disponivel nesta maquina? */
|
|
123
131
|
function disponivel() {
|
|
@@ -152,13 +160,14 @@ function montarComando(pedido) {
|
|
|
152
160
|
if (pedido.contextoRuntime.host !== 'claude-code')
|
|
153
161
|
throw Error('runtime.context.invalid: host Claude esperado');
|
|
154
162
|
const contexto = (0, runtime_context_1.validarContextoRuntime)(pedido.contextoRuntime, pedido.cwd);
|
|
163
|
+
const consultas = [...CONSULTAS_MCP, ...(contexto.grafoMcp ? consultasDoGrafoMcp() : [])];
|
|
155
164
|
// Somente o servidor gerado: nao mesclar MCP de outros escopos nesta sessao filha.
|
|
156
165
|
const permissoes = contexto.permissoesFilho === 'worktree'
|
|
157
|
-
? permissoesDaWorktree(pedido.cwd, contexto.permiteEditarProduto && !plano) : null;
|
|
166
|
+
? permissoesDaWorktree(pedido.cwd, contexto.permiteEditarProduto && !plano, consultas) : null;
|
|
158
167
|
// PLAN não implementa: nem commit, nem claim, nem verify, nem ship, em nenhum perfil. Decidir não é
|
|
159
168
|
// implementar, e o PLAN é onde as decisões nascem (RM-037, achado S7 do CHECK): a decisão autônoma fica.
|
|
160
|
-
const allow = plano ? [...
|
|
161
|
-
: permissoes?.allow ??
|
|
169
|
+
const allow = plano ? [...consultas, 'mcp__orkastery__ork_artifact_write', 'mcp__orkastery__ork_decision_record']
|
|
170
|
+
: permissoes?.allow ?? consultas;
|
|
162
171
|
const deny = [...(plano ? ESCRITA_DE_ARQUIVO : []), ...(permissoes?.deny ?? [])];
|
|
163
172
|
args.push('--plugin-dir', contexto.instalacao, '--strict-mcp-config', '--mcp-config', JSON.stringify({ mcpServers: { orkastery: contexto.servidor } }), '--allowedTools', allow.join(','));
|
|
164
173
|
if (deny.length)
|
|
@@ -189,12 +198,18 @@ function extrairSessionId(saida) {
|
|
|
189
198
|
function listarSessoes(cwd, todas = false, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
190
199
|
return consultarSessoes(cwd, todas, ambiente).sessoes;
|
|
191
200
|
}
|
|
192
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Falha de consulta não pode ser confundida com uma máquina sem sessões. O binário ausente segue
|
|
203
|
+
* falha aqui (o observador e o pulse não concluem nada dele); só o inventário o trata como fonte
|
|
204
|
+
* ausente, com a correção.
|
|
205
|
+
*/
|
|
193
206
|
function consultarSessoes(cwd, todas = false, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
194
207
|
const args = ['agents', '--json'];
|
|
195
208
|
if (todas)
|
|
196
209
|
args.push('--all');
|
|
197
210
|
const r = exec('claude', args, undefined, 60000, ambiente);
|
|
211
|
+
if (r.error === 'ENOENT')
|
|
212
|
+
return { ok: false, ausente: true, sessoes: [], detalhe: 'binário `claude` fora do PATH' };
|
|
198
213
|
if (!r.ok)
|
|
199
214
|
return { ok: false, sessoes: [], detalhe: `claude agents falhou (código ${r.code})` };
|
|
200
215
|
let dados;
|
|
@@ -413,12 +428,25 @@ function casarDuracao(texto, agoraMs) {
|
|
|
413
428
|
}
|
|
414
429
|
const MESES = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
415
430
|
/**
|
|
416
|
-
*
|
|
431
|
+
* RM-037 (fatia 5): o fuso IANA entre parenteses logo depois da hora (`resets 4:40am (<Area>/<Cidade>)`),
|
|
432
|
+
* quando o Intl o aceita. O nome vem do texto do runtime; nenhum nome literal de fuso mora aqui (I-35).
|
|
433
|
+
*/
|
|
434
|
+
function fusoDito(texto, depois) {
|
|
435
|
+
const m = /^\s*\(([A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)*)\)/.exec(texto.slice(depois));
|
|
436
|
+
return m ? (0, horario_1.normalizarFuso)(m[1]) : undefined;
|
|
437
|
+
}
|
|
438
|
+
/** A data do dia seguinte no calendario (mes de 1 a 12), sem passar por fuso nenhum. */
|
|
439
|
+
function diaSeguinte(ano, mes, dia) {
|
|
440
|
+
const d = new Date(Date.UTC(ano, mes - 1, dia + 1));
|
|
441
|
+
return [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()];
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* N5: data e hora de relogio nas formas reais. Codex: `or try again at Sep 22nd, 2026 3:05 PM`
|
|
417
445
|
* (`%b %-d` com sufixo ordinal, `, %Y %-I:%M %p`). Claude Code, quando o reset passa de 24 h:
|
|
418
446
|
* `resets Sep 22, 3pm (<nome IANA do fuso>)` e, em outro ano, `resets Sep 22, 2027, 3:05pm`. Sem ano,
|
|
419
|
-
* vale o ano corrente, ou o seguinte quando a data ja ficou mais de um dia para tras.
|
|
420
|
-
*
|
|
421
|
-
*
|
|
447
|
+
* vale o ano corrente, ou o seguinte quando a data ja ficou mais de um dia para tras. RM-037 (fatia 5):
|
|
448
|
+
* com o fuso entre parenteses, a hora e a desse fuso (`fusoDito`); sem ele, ou com um nome que o Intl
|
|
449
|
+
* recusa, a hora e lida no relogio desta maquina, como antes. `agoraMs` e a ancora: o instante da mensagem.
|
|
422
450
|
*/
|
|
423
451
|
function casarData(texto, agoraMs) {
|
|
424
452
|
const m = texto.match(/(?:try again at|reset[a-z]*(?:\s+at)?)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(?:(\d{4}),?\s*)?(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*([ap]\.?m\.?)?(?![a-z0-9])/i);
|
|
@@ -435,6 +463,16 @@ function casarData(texto, agoraMs) {
|
|
|
435
463
|
hora = 0;
|
|
436
464
|
if (dia < 1 || dia > 31 || hora > 23 || minuto > 59)
|
|
437
465
|
return null;
|
|
466
|
+
const fuso = fusoDito(texto, (m.index ?? 0) + m[0].length);
|
|
467
|
+
if (fuso) {
|
|
468
|
+
const ano = m[3] !== undefined ? Number(m[3]) : Number((0, horario_1.partesLocais)(agoraMs, fuso).ano);
|
|
469
|
+
let alvo = (0, horario_1.instanteNoFuso)(ano, mes + 1, dia, hora, minuto, fuso);
|
|
470
|
+
if (!Number.isFinite(alvo))
|
|
471
|
+
return null;
|
|
472
|
+
if (m[3] === undefined && alvo < agoraMs - 24 * 3600 * 1000)
|
|
473
|
+
alvo = (0, horario_1.instanteNoFuso)(ano + 1, mes + 1, dia, hora, minuto, fuso);
|
|
474
|
+
return Number.isFinite(alvo) ? { resetEm: new Date(alvo).toISOString(), trecho: m[0].trim() } : null;
|
|
475
|
+
}
|
|
438
476
|
const base = new Date(agoraMs);
|
|
439
477
|
const ano = m[3] !== undefined ? Number(m[3]) : base.getFullYear();
|
|
440
478
|
const alvo = new Date(ano, mes, dia, hora, minuto, 0, 0);
|
|
@@ -458,7 +496,17 @@ function casarRelogio(texto, agoraMs) {
|
|
|
458
496
|
hora = 0;
|
|
459
497
|
if (hora > 23 || minuto > 59)
|
|
460
498
|
return null;
|
|
461
|
-
//
|
|
499
|
+
// RM-037 (fatia 5): a proxima ocorrencia daquela hora depois da ancora, no fuso dito pelo runtime.
|
|
500
|
+
const fuso = fusoDito(texto, (m.index ?? 0) + m[0].length);
|
|
501
|
+
if (fuso) {
|
|
502
|
+
const p = (0, horario_1.partesLocais)(agoraMs, fuso);
|
|
503
|
+
let alvo = (0, horario_1.instanteNoFuso)(Number(p.ano), Number(p.mes), Number(p.dia), hora, minuto, fuso);
|
|
504
|
+
if (alvo <= agoraMs)
|
|
505
|
+
alvo = (0, horario_1.instanteNoFuso)(...diaSeguinte(Number(p.ano), Number(p.mes), Number(p.dia)), hora, minuto, fuso);
|
|
506
|
+
if (Number.isFinite(alvo))
|
|
507
|
+
return { resetEm: new Date(alvo).toISOString(), trecho: m[0].trim() };
|
|
508
|
+
}
|
|
509
|
+
// Sem fuso dito: a proxima ocorrencia daquela hora de relogio a partir da ancora, no fuso da maquina.
|
|
462
510
|
const base = new Date(agoraMs);
|
|
463
511
|
const alvo = new Date(base.getFullYear(), base.getMonth(), base.getDate(), hora, minuto, 0, 0);
|
|
464
512
|
if (alvo.getTime() <= agoraMs)
|
package/dist/adapters/codex.js
CHANGED
|
@@ -174,9 +174,10 @@ function consultarRollouts(todas = false, casa = casaDoCodex()) {
|
|
|
174
174
|
continue;
|
|
175
175
|
try {
|
|
176
176
|
const fd = fs.openSync(arquivo, 'r');
|
|
177
|
-
let primeira, cauda;
|
|
177
|
+
let primeira, cauda, atividadeEm;
|
|
178
178
|
try {
|
|
179
|
-
const
|
|
179
|
+
const st = fs.fstatSync(fd), tamanho = st.size;
|
|
180
|
+
atividadeEm = st.mtimeMs;
|
|
180
181
|
const cab = Buffer.alloc(Math.min(tamanho, 65536));
|
|
181
182
|
fs.readSync(fd, cab, 0, cab.length, 0);
|
|
182
183
|
primeira = cab.toString('utf8').split('\n')[0];
|
|
@@ -207,7 +208,7 @@ function consultarRollouts(todas = false, casa = casaDoCodex()) {
|
|
|
207
208
|
catch { /* primeira/última linha do recorte pode estar parcial */ }
|
|
208
209
|
}
|
|
209
210
|
if (todas || state !== 'completed')
|
|
210
|
-
sessoes.push({ sessionId: p.id, cwd: p.cwd, state, kind: 'codex-rollout' });
|
|
211
|
+
sessoes.push({ sessionId: p.id, cwd: p.cwd, state, kind: 'codex-rollout', atividadeEm });
|
|
211
212
|
}
|
|
212
213
|
catch {
|
|
213
214
|
falhar(arquivo, 'leitura ou metadados inválidos ou duplicados');
|
package/dist/agents-md.js
CHANGED
|
@@ -60,6 +60,7 @@ Projeto: **${seguro(manifesto.project.name)}** (${seguro(manifesto.project.abbre
|
|
|
60
60
|
- Edite produto somente na worktree vinculada; estado em \`.orkastery/\` pertence ao núcleo.
|
|
61
61
|
- PLAN não implementa. GO usa commits atômicos. CHECK não corrige. SHIP acontece por \`ork ship\`.
|
|
62
62
|
- Toda alegação exige claim e comando reproduzível; self-report não é evidência.
|
|
63
|
+
- MASTER por omissão só da sua thread: \`ork master <thread> --aceitar-omissao\`; sem a thread, fecha também as entregas de outras frentes.
|
|
63
64
|
${comandos.length ? `\nVerificações do manifesto:\n\n${comandos.join('\n')}\n` : ''}
|
|
64
65
|
Este bloco é mantido por \`ork init\`. Edite livremente o restante do arquivo.
|
|
65
66
|
${exports.FIM_AGENTS_ORK}`;
|
package/dist/auditrun.js
CHANGED
|
@@ -798,7 +798,7 @@ function textoDaRodada(raiz, id) {
|
|
|
798
798
|
L.push('');
|
|
799
799
|
if (r.veredito) {
|
|
800
800
|
L.push(` Claims do auditor: ${r.veredito.ok ? 'SUSTENTADAS' : 'REPROVADAS'} ` +
|
|
801
|
-
`(motivos: ${r.veredito.motivos.join(', ') || 'nenhum'}) no HEAD ${r.veredito.commit
|
|
801
|
+
`(motivos: ${r.veredito.motivos.join(', ') || 'nenhum'}) no HEAD ${(0, util_1.shaCurto)(r.veredito.commit)}`);
|
|
802
802
|
}
|
|
803
803
|
else {
|
|
804
804
|
L.push(` Claims do auditor: NAO verificadas. Rode: ork audit verify ${r.id}`);
|
|
@@ -893,13 +893,14 @@ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
|
|
|
893
893
|
};
|
|
894
894
|
}
|
|
895
895
|
const modo = opcoes.modo ?? manifesto.conduction.default_mode;
|
|
896
|
-
const { thread, gravada } = (0, thread_1.novaThread)(carregado, {
|
|
896
|
+
const { thread, gravada, worktreePor, worktreeFalharia } = (0, thread_1.novaThread)(carregado, {
|
|
897
897
|
nome: opcoes.nome ?? achado.titulo,
|
|
898
898
|
modo,
|
|
899
899
|
slug: opcoes.slug,
|
|
900
900
|
assunto: opcoes.assunto,
|
|
901
901
|
worktree: opcoes.worktree ?? null,
|
|
902
902
|
criarWorktree: opcoes.criarWorktree,
|
|
903
|
+
origemDaWorktree: opcoes.origemDaWorktree,
|
|
903
904
|
dryRun: opcoes.dryRun,
|
|
904
905
|
variante: opcoes.variante ?? null,
|
|
905
906
|
});
|
|
@@ -909,6 +910,8 @@ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
|
|
|
909
910
|
achado,
|
|
910
911
|
thread,
|
|
911
912
|
pedido,
|
|
913
|
+
worktreePor,
|
|
914
|
+
worktreeFalharia,
|
|
912
915
|
motivo: null,
|
|
913
916
|
detalhe: 'simulacao (--dry-run): nenhuma thread e nenhum carimbo no board foram gravados',
|
|
914
917
|
correcao: '',
|
|
@@ -968,6 +971,7 @@ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
|
|
|
968
971
|
achado,
|
|
969
972
|
thread,
|
|
970
973
|
pedido,
|
|
974
|
+
worktreePor,
|
|
971
975
|
motivo: achado.claim.estado === 'nao-verificavel' ? 'claims.unverifiable' : null,
|
|
972
976
|
detalhe: achado.claim.estado === 'nao-verificavel'
|
|
973
977
|
? `AVISO: a claim ${achado.claim.id} do achado nao tem comando de verificacao (motivo tipado claims.unverifiable, que avisa e nao bloqueia)`
|
package/dist/board.js
CHANGED
|
@@ -60,7 +60,7 @@ exports.textoDoBoard = textoDoBoard;
|
|
|
60
60
|
const fs = __importStar(require("node:fs"));
|
|
61
61
|
const path = __importStar(require("node:path"));
|
|
62
62
|
const adapter = __importStar(require("./adapters/claude-bg"));
|
|
63
|
-
const
|
|
63
|
+
const sessoes_contas_1 = require("./sessoes-contas");
|
|
64
64
|
const leases_1 = require("./leases");
|
|
65
65
|
const ledger_1 = require("./ledger");
|
|
66
66
|
const manifest_1 = require("./manifest");
|
|
@@ -102,28 +102,30 @@ function threadsDeTodosOsPerfis(carregado, todos = true) {
|
|
|
102
102
|
const saida = [];
|
|
103
103
|
for (const perfil of perfis) {
|
|
104
104
|
const fila = (0, leases_1.lerFila)(perfil.raiz);
|
|
105
|
+
// RM-036 (D7): uma listagem por perfil. Por thread, a leitura do legado das worktrees se repetiria.
|
|
106
|
+
// I-36: a conducao (`exec:`) tem secao propria; o escalonador segue contando so os leases de escrita.
|
|
107
|
+
const ativos = (0, leases_1.listarLeases)(perfil.raiz).filter((l) => !(0, leases_1.expirado)(l) && (0, leases_1.tipoDoLease)(l.nome) !== 'exec');
|
|
105
108
|
for (const id of (0, thread_1.listarIds)(perfil.raiz)) {
|
|
106
109
|
const thread = (0, thread_1.lerThread)(perfil.raiz, id);
|
|
107
110
|
saida.push({
|
|
108
111
|
perfil: perfil.nome,
|
|
109
112
|
raiz: perfil.raiz,
|
|
110
113
|
thread,
|
|
111
|
-
|
|
112
|
-
leases: (0, leases_1.leasesDaThread)(perfil.raiz, id).filter((l) => (0, leases_1.tipoDoLease)(l.nome) !== 'exec').map((l) => l.nome),
|
|
114
|
+
leases: [...new Set(ativos.filter((l) => l.thread === id).map((l) => l.nome))],
|
|
113
115
|
naFila: fila.filter((p) => p.thread === id),
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
return saida;
|
|
118
120
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Estados de sessao do runtime, ou null quando ele nao pode ser consultado. RM-056 (D7): de todas
|
|
123
|
+
* as contas (a do processo e cada perfil do store), com o fantasma fora da vaga e o codex vivo.
|
|
124
|
+
*/
|
|
125
|
+
function estadosDeSessao(raiz = null, staleMin) {
|
|
121
126
|
if (!adapter.disponivel())
|
|
122
127
|
return null;
|
|
123
|
-
return
|
|
124
|
-
.listarSessoes()
|
|
125
|
-
.filter((s) => !!s.sessionId)
|
|
126
|
-
.map((s) => [s.sessionId, (0, hitl_1.estadoBruto)(s)]));
|
|
128
|
+
return (0, sessoes_contas_1.estadosNasContas)(raiz, { staleMin });
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
131
|
* RM-037 (rm037defeito, defeito 3): o portao de vaga do `ork phase run`. Com `max_parallel_threads: 5`,
|
|
@@ -210,7 +212,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
210
212
|
const max = Math.max(1, manifesto.concurrency.max_parallel_threads);
|
|
211
213
|
const staleMin = manifesto.concurrency.stale_after_min;
|
|
212
214
|
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
213
|
-
const estados = opcoes.estados === undefined ? estadosDeSessao() : opcoes.estados;
|
|
215
|
+
const estados = opcoes.estados === undefined ? estadosDeSessao(raiz, staleMin) : opcoes.estados;
|
|
214
216
|
const fila = (0, leases_1.lerFila)(raiz);
|
|
215
217
|
// I-36: a conducao (`exec:`) nao entra na conta de vagas: a ocupacao da sessao ja sai do ledger
|
|
216
218
|
// e do runtime (`avaliarOcupacao`), e conta-la de novo mudaria o escalonador.
|
|
@@ -231,7 +233,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
231
233
|
if (t.status === 'fechada' || (0, sessoes_adopt_1.ehRegistroDeAdocao)(t))
|
|
232
234
|
continue;
|
|
233
235
|
const oc = ocupacoes.get(t.id);
|
|
234
|
-
const meus = ativos.filter((l) => l.thread === t.id).map((l) => l.nome);
|
|
236
|
+
const meus = [...new Set(ativos.filter((l) => l.thread === t.id).map((l) => l.nome))];
|
|
235
237
|
const ocupa = oc.ocupaVaga || (meus.length > 0 && oc.classe === 'ociosa');
|
|
236
238
|
if (!ocupa)
|
|
237
239
|
continue;
|
|
@@ -254,7 +256,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
254
256
|
for (const t of threads) {
|
|
255
257
|
if (emAndamento.has(t.id))
|
|
256
258
|
continue;
|
|
257
|
-
const meus = ativos.filter((l) => l.thread === t.id).map((l) => l.nome);
|
|
259
|
+
const meus = [...new Set(ativos.filter((l) => l.thread === t.id).map((l) => l.nome))];
|
|
258
260
|
if (t.status === 'fechada') {
|
|
259
261
|
vagas.push({
|
|
260
262
|
thread: t.id,
|
|
@@ -286,7 +288,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
286
288
|
situacao: 'pausada',
|
|
287
289
|
motivo: 'human.pending',
|
|
288
290
|
detalhe: oc.detalhe,
|
|
289
|
-
correcao: `ork gate request ${t.id}`,
|
|
291
|
+
correcao: oc.correcao ?? `ork gate request ${t.id}`,
|
|
290
292
|
leases: meus,
|
|
291
293
|
});
|
|
292
294
|
continue;
|
|
@@ -385,7 +387,7 @@ function devolverVagas(carregado, opcoes = {}) {
|
|
|
385
387
|
const { raiz, manifesto } = carregado;
|
|
386
388
|
const staleMin = manifesto.concurrency.stale_after_min;
|
|
387
389
|
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
388
|
-
const estados = opcoes.estados === undefined ? estadosDeSessao() : opcoes.estados;
|
|
390
|
+
const estados = opcoes.estados === undefined ? estadosDeSessao(raiz, staleMin) : opcoes.estados;
|
|
389
391
|
const quem = opcoes.por ?? 'escalonador (ork board reap)';
|
|
390
392
|
const devolvidas = [];
|
|
391
393
|
for (const id of (0, thread_1.listarIds)(raiz)) {
|