@orkastery/cli 0.5.3 → 0.5.6
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 +158 -0
- package/adapters/claude-code/mods/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
- package/adapters/openclaw/README.md +2 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/dist/adapters/claude-bg.js +77 -13
- package/dist/adapters/codex.js +10 -1
- package/dist/agents-md.js +24 -15
- package/dist/auditrun.js +17 -2
- package/dist/board.js +4 -3
- package/dist/branch-de-estado.js +23 -1
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +13 -7
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +3 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +36 -1
- package/dist/conducao-sentinela.js +388 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +122 -30
- package/dist/conhecimento.js +490 -0
- package/dist/docs.js +22 -8
- package/dist/doctor-pulse-cron.js +182 -27
- package/dist/doctor-rede.js +18 -17
- package/dist/doctor.js +115 -111
- package/dist/entrega-pr.js +22 -21
- package/dist/escopo-escrita.js +10 -1
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +38 -8
- package/dist/fabrica-publicar.js +3 -1
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +1 -1
- package/dist/gates.js +3 -28
- package/dist/handoff.js +10 -6
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-resumo.js +29 -3
- package/dist/hitl-tempo-parado.js +4 -4
- package/dist/hitl.js +29 -33
- package/dist/horario.js +14 -7
- package/dist/hosts.js +52 -1
- package/dist/impedimento.js +108 -14
- package/dist/index.js +623 -521
- package/dist/indice.js +7 -1
- package/dist/init.js +43 -47
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +92 -21
- package/dist/intelligence-graph-contexto.js +56 -17
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/ledger.js +15 -1
- package/dist/liveness.js +6 -3
- package/dist/locale-ajuda.js +761 -0
- package/dist/locale-gerados.js +160 -0
- package/dist/locale-master.js +99 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-pulse.js +211 -0
- package/dist/locale-retry.js +385 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-ship.js +494 -0
- package/dist/locale-thread.js +277 -0
- package/dist/locale-verify.js +125 -0
- package/dist/locale.js +714 -0
- package/dist/manifest.js +129 -3
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +4 -6
- package/dist/master.js +21 -24
- package/dist/mcp-artifacts.js +11 -2
- package/dist/mcp-git.js +23 -6
- package/dist/mcp-grafo.js +7 -4
- package/dist/mcp-server.js +8 -2
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +5 -2
- package/dist/noctum-arco.js +911 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-envelope.js +810 -0
- package/dist/noctum-execucao.js +304 -0
- package/dist/noctum-fila.js +280 -0
- package/dist/ocupacao.js +29 -2
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +9 -2
- package/dist/orquestracao.js +13 -12
- package/dist/parado-no-condutor.js +34 -9
- package/dist/phase.js +94 -39
- package/dist/policies.js +4 -4
- package/dist/portfolio.js +4 -2
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +127 -4
- package/dist/prompts.js +6 -2
- package/dist/pulse-delivery.js +13 -2
- package/dist/pulse.js +52 -19
- package/dist/ratelimit.js +4 -2
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-status.js +3 -2
- package/dist/retry.js +232 -148
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +129 -33
- package/dist/runtime-context.js +181 -3
- package/dist/runtimes.js +2 -1
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +7 -1
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +1 -1
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +84 -81
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +153 -82
- package/dist/util.js +8 -1
- package/dist/verify.js +141 -52
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +165 -0
- package/dist/worktree.js +20 -0
- package/dist/yaml.js +27 -8
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// A fila da Mesa do Dono (RM-059 F5), em funcoes puras: o que o pulse, o retrato e o `ork_hitl_pending` do nucleo
|
|
2
|
+
// trazem vira pergunta, parada ou decisao, e a faixa sai daqui. So importa tipos: o register.tsx le o nucleo pela
|
|
3
|
+
// ponte e entrega os objetos a estas funcoes. Nada aqui responde pedido nem escolhe opcao pelo dono.
|
|
4
|
+
import type { MesaAlternativa, MesaCanal, MesaDecisao, MesaFila, MesaObvia, MesaOutro, MesaPergunta } from '../types'
|
|
5
|
+
|
|
6
|
+
/** Perguntas por pagina: o lote de ate cinco da RM-041 e da RM-057. */
|
|
7
|
+
export const TAMANHO_DO_LOTE = 5
|
|
8
|
+
/** Decisoes informadas que o painel mostra por inteiro; o resto vira uma linha com a contagem. */
|
|
9
|
+
export const MAXIMO_DE_DECISOES = 5
|
|
10
|
+
/** Paradas e impedimentos que o painel mostra por inteiro. */
|
|
11
|
+
export const MAXIMO_DE_OUTROS = 5
|
|
12
|
+
/** So a decisao das ultimas 24 h que a Mesa ainda nao mostrou conta como nova. */
|
|
13
|
+
export const JANELA_DE_NOVAS_MS = 24 * 60 * 60 * 1000
|
|
14
|
+
export const TETO_DO_TEXTO = 200
|
|
15
|
+
export const TETO_DA_FAIXA = 200
|
|
16
|
+
const ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
17
|
+
|
|
18
|
+
export const FILA_VAZIA: MesaFila = { perguntas: [], outros: [], decisoes: [], totalDeDecisoes: 0, lidoEm: null, erro: null, avisos: [] }
|
|
19
|
+
|
|
20
|
+
type Bruto = Record<string, unknown>
|
|
21
|
+
const objeto = (v: unknown): v is Bruto => !!v && typeof v === 'object' && !Array.isArray(v)
|
|
22
|
+
const texto = (v: unknown): string => (typeof v === 'string' ? v : '')
|
|
23
|
+
const textoOuNulo = (v: unknown): string | null => (typeof v === 'string' && v.trim() !== '' ? v : null)
|
|
24
|
+
|
|
25
|
+
/** Uma linha so, sem quebra e sem espaco repetido, cortada no teto. */
|
|
26
|
+
export function umaLinha(v: unknown, teto = TETO_DO_TEXTO): string {
|
|
27
|
+
const t = texto(v).replace(/\s+/g, ' ').trim()
|
|
28
|
+
return t.length <= teto ? t : `${t.slice(0, teto - 1).trimEnd()}…`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Quanto tempo, como o dono le: segundos, minutos, horas e dias. */
|
|
32
|
+
export function duracao(ms: number): string {
|
|
33
|
+
const s = Math.max(0, Math.floor(ms / 1000))
|
|
34
|
+
if (s < 60) return `${s} s`
|
|
35
|
+
const min = Math.floor(s / 60)
|
|
36
|
+
if (min < 60) return `${min} min`
|
|
37
|
+
const h = Math.floor(min / 60)
|
|
38
|
+
if (h < 48) return `${h} h`
|
|
39
|
+
return `${Math.floor(h / 24)} dias`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** O texto que um JSON traz, ou `undefined` quando nao e JSON. */
|
|
43
|
+
function lerJson(t: string): unknown {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(t)
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** O JSON que uma ferramenta MCP do nucleo devolve no primeiro bloco de texto; erro vira excecao com o motivo. */
|
|
52
|
+
export function jsonDoMcp(r: unknown): unknown {
|
|
53
|
+
const bruto = (objeto(r) ? r : {}) as { content?: unknown; isError?: unknown }
|
|
54
|
+
const blocos = Array.isArray(bruto.content) ? bruto.content.filter(objeto) : []
|
|
55
|
+
const corpo = blocos.find(c => c.type === 'text' && typeof c.text === 'string')?.text as string | undefined
|
|
56
|
+
if (bruto.isError === true) {
|
|
57
|
+
const j = corpo === undefined ? undefined : lerJson(corpo)
|
|
58
|
+
throw new Error(umaLinha(objeto(j) && typeof j.erro === 'string' ? j.erro : corpo ?? 'o nucleo respondeu com erro'))
|
|
59
|
+
}
|
|
60
|
+
if (corpo === undefined) throw new Error('o nucleo nao devolveu texto')
|
|
61
|
+
const j = lerJson(corpo)
|
|
62
|
+
if (j === undefined) throw new Error('o nucleo devolveu texto que nao e JSON')
|
|
63
|
+
return j
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** As alternativas de um pedido: letras no `ork.hitl/v2`, numeros no `ork.hitl/v1`, como o nucleo as declara. */
|
|
67
|
+
export function alternativasDoPedido(p: Bruto): MesaAlternativa[] {
|
|
68
|
+
if (p.contrato === 'ork.hitl/v2' && Array.isArray(p.alternativas)) {
|
|
69
|
+
return p.alternativas.filter(objeto).map(a => ({
|
|
70
|
+
chave: texto(a.letra), texto: umaLinha(a.texto), acao: texto(a.acao), consequencia: textoOuNulo(a.consequencia),
|
|
71
|
+
recomendada: a.recomendada === true, porque: textoOuNulo(a.porque),
|
|
72
|
+
}))
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(p.opcoes)) {
|
|
75
|
+
return p.opcoes.filter(objeto).map(o => ({
|
|
76
|
+
chave: typeof o.numero === 'number' ? String(o.numero) : texto(o.numero), texto: umaLinha(o.texto), acao: texto(o.acao),
|
|
77
|
+
consequencia: null, recomendada: false, porque: null,
|
|
78
|
+
}))
|
|
79
|
+
}
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A pergunta de um pedido aberto (`ork.hitl/v1`, ou `ork.hitl/v2` de classe `pergunta`); `null` para o que nao e
|
|
85
|
+
* pergunta (a decisao informada nao pede nada) ou nao tem identificacao valida.
|
|
86
|
+
*/
|
|
87
|
+
export function perguntaDoPedido(pedido: unknown, extra: { desdeEm?: string | null; reserva?: string | null } = {}): MesaPergunta | null {
|
|
88
|
+
if (!objeto(pedido)) return null
|
|
89
|
+
const v2 = pedido.contrato === 'ork.hitl/v2'
|
|
90
|
+
if (!v2 && pedido.contrato !== 'ork.hitl/v1') return null
|
|
91
|
+
if (v2 && pedido.classe !== 'pergunta') return null
|
|
92
|
+
const alvo = objeto(pedido.alvo) ? pedido.alvo.tipo : undefined
|
|
93
|
+
if (alvo !== 'gate' && alvo !== 'session') return null
|
|
94
|
+
const thread = texto(pedido.thread)
|
|
95
|
+
const id = texto(pedido.id)
|
|
96
|
+
if (!ID.test(thread) || !ID.test(id)) return null
|
|
97
|
+
const alternativas = alternativasDoPedido(pedido)
|
|
98
|
+
const recomendada = alternativas.find(a => a.recomendada)
|
|
99
|
+
const codigo = v2 ? textoOuNulo(pedido.codigo) : null
|
|
100
|
+
return {
|
|
101
|
+
thread, fase: textoOuNulo(pedido.fase), modo: textoOuNulo(pedido.modo), pedidoId: id, codigo,
|
|
102
|
+
contrato: v2 ? 'ork.hitl/v2' : 'ork.hitl/v1', alvo, motivo: texto(pedido.motivo), pergunta: umaLinha(pedido.pergunta),
|
|
103
|
+
recomendacao: v2 ? (recomendada ? `${recomendada.chave}) ${recomendada.texto}` : '') : umaLinha(pedido.recomendacao),
|
|
104
|
+
alternativas, irreversivel: pedido.irreversivel === true, ato: textoOuNulo(pedido.ato),
|
|
105
|
+
tipoDeResposta: v2 ? textoOuNulo(pedido.tipoDeResposta) : null,
|
|
106
|
+
respostaAceita: objeto(pedido.respostaAceita) ? textoOuNulo(pedido.respostaAceita.tipo) : null,
|
|
107
|
+
dependenciaTecnica: pedido.dependenciaTecnica !== undefined, acaoAoExpirar: textoOuNulo(pedido.acaoPadraoAoExpirar),
|
|
108
|
+
prazo: textoOuNulo(pedido.prazo), prazoLocal: null, desdeEm: extra.desdeEm ?? textoOuNulo(pedido.criadoEm),
|
|
109
|
+
estado: 'desconhecido', conferida: false, canalClaudeCode: 'desconhecido',
|
|
110
|
+
reserva: extra.reserva ?? (codigo && recomendada ? `${codigo} ${recomendada.chave}` : null),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** As chaves que a Mesa ja registrou na ponte por um canal (`decisao:` no painel, `hitl:` na notificacao), sem o prefixo. */
|
|
115
|
+
function chavesDaMesa(registro: unknown, prefixo: string, canal?: string): Set<string> {
|
|
116
|
+
const itens = objeto(registro) && Array.isArray(registro.itens) ? registro.itens.filter(objeto) : []
|
|
117
|
+
const chaves = new Set<string>()
|
|
118
|
+
for (const i of itens) {
|
|
119
|
+
const chave = texto(i.chave)
|
|
120
|
+
if (i.mod === 'ork-mesa' && chave.startsWith(prefixo) && (canal === undefined || i.canal === canal)) chaves.add(chave.slice(prefixo.length))
|
|
121
|
+
}
|
|
122
|
+
return chaves
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Os ids das decisoes que a Mesa ja mostrou ao dono, pelo registro do mostrado da ponte. */
|
|
126
|
+
export function decisoesVistas(registro: unknown): Set<string> {
|
|
127
|
+
return chavesDaMesa(registro, 'decisao:')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Os pedidos que a Mesa ja levou ao dono por notificacao, pelo registro da ponte: valem depois de uma recarga do modulo. */
|
|
131
|
+
export function pedidosNotificados(registro: unknown): Set<string> {
|
|
132
|
+
return chavesDaMesa(registro, 'hitl:', 'notificacao')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A ordem das perguntas: o prazo mais perto primeiro, depois a mais antiga; sem prazo vai para o fim. */
|
|
136
|
+
export function ordenarPerguntas(perguntas: readonly MesaPergunta[]): MesaPergunta[] {
|
|
137
|
+
const prazo = (p: MesaPergunta) => {
|
|
138
|
+
const t = Date.parse(p.prazo ?? '')
|
|
139
|
+
return Number.isFinite(t) ? t : Number.POSITIVE_INFINITY
|
|
140
|
+
}
|
|
141
|
+
const desde = (p: MesaPergunta) => {
|
|
142
|
+
const t = Date.parse(p.desdeEm ?? '')
|
|
143
|
+
return Number.isFinite(t) ? t : Number.POSITIVE_INFINITY
|
|
144
|
+
}
|
|
145
|
+
return [...perguntas].sort((a, b) => prazo(a) - prazo(b) || desde(a) - desde(b) || a.pedidoId.localeCompare(b.pedidoId))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A fila a partir do `ork pulse --json`: as perguntas com pedido, as paradas e os impedimentos de
|
|
150
|
+
* `precisaDeHumanoAgora`, e as decisoes que a fabrica tomou pelo dono na janela do pulse.
|
|
151
|
+
*/
|
|
152
|
+
export function filaDoPulse(pulse: unknown, vistas: ReadonlySet<string>, agora: number): Omit<MesaFila, 'lidoEm' | 'erro' | 'avisos'> {
|
|
153
|
+
const p = objeto(pulse) ? pulse : {}
|
|
154
|
+
const humanos = Array.isArray(p.precisaDeHumanoAgora) ? p.precisaDeHumanoAgora.filter(objeto) : []
|
|
155
|
+
const perguntas: MesaPergunta[] = []
|
|
156
|
+
const outros: MesaOutro[] = []
|
|
157
|
+
for (const item of humanos) {
|
|
158
|
+
const pergunta = item.pedido === undefined ? null
|
|
159
|
+
: perguntaDoPedido(item.pedido, { desdeEm: textoOuNulo(item.desdeEm), reserva: textoOuNulo(item.comandoResposta) })
|
|
160
|
+
if (pergunta) {
|
|
161
|
+
if (!perguntas.some(x => x.pedidoId === pergunta.pedidoId)) perguntas.push(pergunta)
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
if (item.classe === 'score_pendente') continue
|
|
165
|
+
const impedimento = objeto(item.impedimento) ? item.impedimento : null
|
|
166
|
+
outros.push({
|
|
167
|
+
chave: umaLinha(item.id, 160), thread: textoOuNulo(item.thread), fase: textoOuNulo(item.fase), motivo: texto(item.motivo),
|
|
168
|
+
texto: umaLinha(item.pergunta), recomendacao: umaLinha(item.recomendacao),
|
|
169
|
+
comando: umaLinha(impedimento ? impedimento.comando : item.comandoResposta), desdeEm: textoOuNulo(item.desdeEm),
|
|
170
|
+
impedimento: impedimento !== null,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
const brutas = Array.isArray(p.decisoes) ? p.decisoes.filter(objeto) : []
|
|
174
|
+
// A decisao que outra desfez continua na janela, marcada como revertida, e nunca conta como nova.
|
|
175
|
+
const revertidaPor = new Map<string, string>()
|
|
176
|
+
for (const d of brutas) if (typeof d.reverte === 'string' && ID.test(d.reverte)) revertidaPor.set(d.reverte, texto(d.id))
|
|
177
|
+
const decisoes: MesaDecisao[] = brutas.flatMap(d => {
|
|
178
|
+
const id = texto(d.id)
|
|
179
|
+
if (!ID.test(id)) return []
|
|
180
|
+
const custo = objeto(d.custoDeReverter) ? d.custoDeReverter : {}
|
|
181
|
+
const em = texto(d.em)
|
|
182
|
+
const recente = Number.isFinite(Date.parse(em)) && agora - Date.parse(em) <= JANELA_DE_NOVAS_MS
|
|
183
|
+
const desfeita = revertidaPor.get(id) ?? null
|
|
184
|
+
return [{
|
|
185
|
+
id, thread: texto(d.thread), fase: texto(d.fase), em, decidido: umaLinha(d.decidido), porque: umaLinha(d.porque),
|
|
186
|
+
comoMudar: umaLinha(d.comoMudar), custoAgora: umaLinha(custo.agora, 140), custoDepois: umaLinha(custo.depois, 140),
|
|
187
|
+
quemDecidiu: umaLinha(d.quemDecidiu, 120), reverte: typeof d.reverte === 'string' && ID.test(d.reverte) ? d.reverte : null,
|
|
188
|
+
revertidaPor: desfeita, nova: recente && desfeita === null && !vistas.has(id),
|
|
189
|
+
}]
|
|
190
|
+
})
|
|
191
|
+
decisoes.sort((a, b) => Number(b.nova) - Number(a.nova) || b.em.localeCompare(a.em))
|
|
192
|
+
return { perguntas: ordenarPerguntas(perguntas), outros, decisoes, totalDeDecisoes: decisoes.length }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* A assinatura do retrato da secao `hitl`: cada pedido com o estado dele. Muda quando um pedido abre, e respondido,
|
|
197
|
+
* vence ou fica velho, e e o sinal barato para reler o pulse.
|
|
198
|
+
*/
|
|
199
|
+
export function assinaturaDoRetrato(retrato: unknown): string {
|
|
200
|
+
const secoes = objeto(retrato) && objeto(retrato.sections) ? retrato.sections : {}
|
|
201
|
+
const hitl = objeto(secoes.hitl) ? secoes.hitl : {}
|
|
202
|
+
const itens = Array.isArray(hitl.items) ? hitl.items.filter(objeto) : []
|
|
203
|
+
return itens.map(i => `${texto(i.id)}:${texto(i.status)}`).sort().join(',')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Confere a pergunta no `ork_hitl_pending` da thread: o pedido de agora, o estado, o prazo local e a oferta do canal
|
|
208
|
+
* `claude-code`. Pedido que o nucleo nao lista mais saiu da fila (respondido, substituido ou de outra fase): `null`.
|
|
209
|
+
*/
|
|
210
|
+
export function conferirNoNucleo(pergunta: MesaPergunta, resposta: unknown): MesaPergunta | null {
|
|
211
|
+
const pendencias = objeto(resposta) && Array.isArray(resposta.pendencias) ? resposta.pendencias.filter(objeto) : []
|
|
212
|
+
const achada = pendencias.find(x => objeto(x.pedido) && x.pedido.id === pergunta.pedidoId)
|
|
213
|
+
if (!achada) return null
|
|
214
|
+
const fresca = perguntaDoPedido(achada.pedido, { desdeEm: pergunta.desdeEm, reserva: pergunta.reserva })
|
|
215
|
+
if (!fresca) return null
|
|
216
|
+
const canais = Array.isArray(achada.canais) ? achada.canais.filter(objeto) : []
|
|
217
|
+
const claudeCode = canais.find(c => c.canal === 'claude-code')
|
|
218
|
+
const canal: MesaCanal = claudeCode?.estado === 'disponivel' ? 'disponivel' : 'indisponivel'
|
|
219
|
+
return { ...fresca, estado: textoOuNulo(achada.estado) ?? 'desconhecido', conferida: true, prazoLocal: textoOuNulo(achada.prazoLocal),
|
|
220
|
+
canalClaudeCode: canal }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** As threads que tem pergunta na fila, sem repetir, na ordem da fila. */
|
|
224
|
+
export function threadsComPergunta(perguntas: readonly MesaPergunta[]): string[] {
|
|
225
|
+
return [...new Set(perguntas.map(p => p.thread))]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* O que espera o dono de fato: a pergunta que a Mesa decidiu pelo nucleo nao conta, nem a que ainda confere no nucleo
|
|
230
|
+
* se a decisao chegou (ela volta a esperar o dono se a leitura seguinte nao a mostrar).
|
|
231
|
+
*/
|
|
232
|
+
export function esperamODono(fila: MesaFila, obvias: Readonly<Record<string, MesaObvia>>): MesaPergunta[] {
|
|
233
|
+
return fila.perguntas.filter(p => {
|
|
234
|
+
const estado = obvias[p.pedidoId]?.estado
|
|
235
|
+
return estado !== 'registrada' && estado !== 'incerta'
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A linha da Mesa na faixa: quantos pedidos esperam o dono (com a thread do primeiro), as paradas e as decisoes novas
|
|
241
|
+
* tomadas por ele. Sem idade no texto, para a linha so mudar quando a fila muda. Sem nada, a linha sai.
|
|
242
|
+
*/
|
|
243
|
+
export function textoDaFaixa(fila: MesaFila, obvias: Readonly<Record<string, MesaObvia>>): string | undefined {
|
|
244
|
+
if (fila.lidoEm === null) return undefined
|
|
245
|
+
const partes: string[] = []
|
|
246
|
+
const esperam = esperamODono(fila, obvias)
|
|
247
|
+
if (esperam.length) partes.push(`${esperam.length} ${esperam.length === 1 ? 'espera' : 'esperam'} você (${esperam[0]?.thread})`)
|
|
248
|
+
if (fila.outros.length) partes.push(`${fila.outros.length} ${fila.outros.length === 1 ? 'parada' : 'paradas'}`)
|
|
249
|
+
const novas = fila.decisoes.filter(d => d.nova).length
|
|
250
|
+
if (novas) partes.push(`${novas} ${novas === 1 ? 'decidida' : 'decididas'} sem te chamar`)
|
|
251
|
+
if (!partes.length) return undefined
|
|
252
|
+
return umaLinha(`mesa: ${partes.join(' · ')}`, TETO_DA_FAIXA)
|
|
253
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// O teste das quatro condicoes da decisao obvia (M4 do dono, RM-059; D5 da thread ork-rm059mesa), em funcao pura.
|
|
2
|
+
// E o mesmo teste do nucleo (`classificarDecisao` em core/src/hitl-contract.ts, I-41): as portas fechadas primeiro
|
|
3
|
+
// (irreversivel, dinheiro, escopo ou produto), depois as quatro condicoes na ordem do GOAL. A Mesa so calcula os sinais
|
|
4
|
+
// a partir do pedido que o nucleo serve; o teste do nucleo confere que a classe sai igual a do `classificarDecisao`.
|
|
5
|
+
// Decidir, para a Mesa, e registrar a decisao autonoma do nucleo (`ork_decision_record`): nunca responder o pedido. Um
|
|
6
|
+
// pedido aberto so se resolve pela resposta humana, pela substituicao, pelo prazo com `seguir-recomendada` ou pelo
|
|
7
|
+
// fechamento da thread; por isso o criterio escrito que a Mesa le pelo nucleo e o proprio pedido declarar que segue a
|
|
8
|
+
// recomendada no prazo. Falhou uma condicao, e pergunta. So importa tipos.
|
|
9
|
+
import type { MesaAlternativa, MesaPergunta } from '../types'
|
|
10
|
+
|
|
11
|
+
/** As portas fechadas e as quatro condicoes, na ordem do `classificarDecisao` do nucleo. */
|
|
12
|
+
export type Condicao = 'irreversivel' | 'dinheiro' | 'escopo' | 'criterio' | 'alternativas' | 'reversivel' | 'barato'
|
|
13
|
+
export const CONDICOES: readonly Condicao[] = ['irreversivel', 'dinheiro', 'escopo', 'criterio', 'alternativas', 'reversivel', 'barato']
|
|
14
|
+
|
|
15
|
+
/** Como o cartao diz o que impediu a decisao obvia. */
|
|
16
|
+
export const FALHA_DA_CONDICAO: Readonly<Record<Condicao, string>> = {
|
|
17
|
+
irreversivel: 'o ato é irreversível',
|
|
18
|
+
dinheiro: 'o ato gasta dinheiro',
|
|
19
|
+
escopo: 'é decisão de escopo ou produto (GOAL, PLAN ou política)',
|
|
20
|
+
criterio: 'não há critério escrito que a Mesa leia pelo núcleo',
|
|
21
|
+
alternativas: 'as alternativas não são estritamente piores pelo critério',
|
|
22
|
+
reversivel: 'não dá para desfazer antes da entrega',
|
|
23
|
+
barato: 'errar não sai barato, ou só o dono resolve',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** As fases antes da entrega: depois do CHECK, o que se decide vai para a `main`. */
|
|
27
|
+
export const FASES_ANTES_DA_ENTREGA: readonly string[] = ['GOAL', 'PLAN', 'GO', 'CHECK']
|
|
28
|
+
/**
|
|
29
|
+
* GOAL e PLAN decidem objetivo, premissa e plano: o que se pergunta ali muda escopo ou produto, e e sempre do dono
|
|
30
|
+
* (M4). Por isso o nucleo abre esses pedidos em profundidade `profunda` (`profundidadeDoPedido`).
|
|
31
|
+
*/
|
|
32
|
+
export const FASES_DE_ESCOPO: readonly string[] = ['GOAL', 'PLAN']
|
|
33
|
+
/** Credencial so o dono entrega; o erro de decidir por ele nao e barato. */
|
|
34
|
+
export const MOTIVOS_SO_DO_DONO: readonly string[] = ['hitl.credencial']
|
|
35
|
+
|
|
36
|
+
/** Os sinais do teste, na forma do `SinaisDaDecisao` do nucleo. */
|
|
37
|
+
export type SinaisDaObvia = {
|
|
38
|
+
criterio?: { tipo: 'medicao'; referencia: string }
|
|
39
|
+
alternativasEstritamentePiores: boolean
|
|
40
|
+
reversivelAntesDaEntrega: boolean
|
|
41
|
+
erroBaratoEDetectavel: boolean
|
|
42
|
+
irreversivel: boolean
|
|
43
|
+
gastaDinheiro: boolean
|
|
44
|
+
mudaEscopoOuProduto: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ResultadoDaObvia =
|
|
48
|
+
| { obvia: true; alternativa: MesaAlternativa; referencia: string }
|
|
49
|
+
| { obvia: false; falhou: Condicao[] }
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A referencia do criterio que a Mesa cita: a medicao como comando que o CHECK roda contra o ledger, onde o pedido fica
|
|
53
|
+
* escrito para sempre (sai 0 so se o pedido declara seguir a recomendada no prazo).
|
|
54
|
+
*/
|
|
55
|
+
export function referenciaDoCriterio(p: MesaPergunta): string {
|
|
56
|
+
return `grep -F '"id":"${p.pedidoId}"' .orkastery/threads/${p.thread}/ledger.jsonl | grep -qF '"acaoPadraoAoExpirar":"seguir-recomendada"'`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Os sinais a partir do pedido servido pelo nucleo e do instante da leitura, em ms. */
|
|
60
|
+
export function sinaisDaPergunta(p: MesaPergunta, agora: number): SinaisDaObvia {
|
|
61
|
+
const recomendadas = p.alternativas.filter(a => a.recomendada).length
|
|
62
|
+
const prazo = Date.parse(p.prazo ?? '')
|
|
63
|
+
return {
|
|
64
|
+
// Criterio escrito: o pedido v2 declara, no ledger, que a fabrica segue a recomendada no prazo.
|
|
65
|
+
...(p.contrato === 'ork.hitl/v2' && p.acaoAoExpirar === 'seguir-recomendada' ? { criterio: { tipo: 'medicao', referencia: referenciaDoCriterio(p) } } : {}),
|
|
66
|
+
// Estritamente piores pelo criterio: ha escolha, e o criterio aponta uma so.
|
|
67
|
+
alternativasEstritamentePiores: p.alternativas.length >= 2 && recomendadas === 1,
|
|
68
|
+
// Desfaz antes da entrega: a fase ainda nao entrega (o ato sem volta ja fecha a porta antes).
|
|
69
|
+
reversivelAntesDaEntrega: FASES_ANTES_DA_ENTREGA.includes(p.fase ?? ''),
|
|
70
|
+
// Barato e detectavel: o pedido conferido agora no nucleo (sem isso a evidencia seria inventada), gate (responder
|
|
71
|
+
// sessao e falar pelo dono), escolha objetiva, nada que so o dono resolve, o pedido ainda aberto e o prazo por vir,
|
|
72
|
+
// para o dono poder mudar antes.
|
|
73
|
+
erroBaratoEDetectavel: p.conferida && p.alvo === 'gate' && p.tipoDeResposta === 'objetiva' && p.respostaAceita === 'opcao' &&
|
|
74
|
+
!p.dependenciaTecnica && !MOTIVOS_SO_DO_DONO.includes(p.motivo) && p.estado === 'aberto' && Number.isFinite(prazo) && prazo > agora,
|
|
75
|
+
irreversivel: p.irreversivel || p.ato !== null,
|
|
76
|
+
gastaDinheiro: p.ato === 'dinheiro' || p.motivo === 'cost.violation',
|
|
77
|
+
mudaEscopoOuProduto: p.motivo === 'policy.violation' || FASES_DE_ESCOPO.includes(p.fase ?? ''),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* O teste: as portas fechadas e as quatro condicoes, na ordem do nucleo. Devolve a alternativa recomendada e a
|
|
83
|
+
* referencia do criterio quando a decisao e obvia, ou tudo que falhou, na ordem.
|
|
84
|
+
*/
|
|
85
|
+
export function testeDaObvia(p: MesaPergunta, agora: number): ResultadoDaObvia {
|
|
86
|
+
const s = sinaisDaPergunta(p, agora)
|
|
87
|
+
const falhou: Condicao[] = []
|
|
88
|
+
if (s.irreversivel) falhou.push('irreversivel')
|
|
89
|
+
if (s.gastaDinheiro) falhou.push('dinheiro')
|
|
90
|
+
if (s.mudaEscopoOuProduto) falhou.push('escopo')
|
|
91
|
+
if (!s.criterio) falhou.push('criterio')
|
|
92
|
+
if (!s.alternativasEstritamentePiores) falhou.push('alternativas')
|
|
93
|
+
if (!s.reversivelAntesDaEntrega) falhou.push('reversivel')
|
|
94
|
+
if (!s.erroBaratoEDetectavel) falhou.push('barato')
|
|
95
|
+
const recomendada = p.alternativas.find(a => a.recomendada)
|
|
96
|
+
if (falhou.length || !s.criterio || !recomendada) return { obvia: false, falhou: falhou.length ? falhou : ['alternativas'] }
|
|
97
|
+
return { obvia: true, alternativa: recomendada, referencia: s.criterio.referencia }
|
|
98
|
+
}
|