@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,344 @@
|
|
|
1
|
+
// O painel da Torre de Controle (RM-059, fatia F1; ficha M3 do plano dos mods). Uma linha por thread aberta
|
|
2
|
+
// desta maquina, montada so do retrato da thread (`ork.maestro-snapshot/v1`) que a ponte entrega por
|
|
3
|
+
// `$.ork.retrato`: o trilho das seis fases, o modo, quem conduz e desde quando, o bloqueio tipado numa frase,
|
|
4
|
+
// os pedidos ao dono e as acoes que o nucleo declara disponiveis. Modulo puro: o `$` fica no register.tsx, e
|
|
5
|
+
// o desenho recebe a tabela de elementos que a superficie resolveu.
|
|
6
|
+
import type {
|
|
7
|
+
TorreAcao, TorreEstado, TorreFase, TorreLinha, TorreLinhaDaRede, TorreMostrado, TorrePedido, TorreQuadro, TorreRede, TorreSessao,
|
|
8
|
+
} from '../types'
|
|
9
|
+
import { fraseDoMotivo, MOTIVOS_DO_DONO } from './frases'
|
|
10
|
+
|
|
11
|
+
export const FASES: readonly TorreFase[] = ['GOAL', 'PLAN', 'GO', 'CHECK', 'SHIP', 'MASTER']
|
|
12
|
+
/** A ordem das linhas (D6): o que pede acao do dono primeiro. */
|
|
13
|
+
export const ORDEM_DO_ESTADO: readonly TorreEstado[] = ['espera-voce', 'parada', 'andando', 'quieta', 'sem-leitura']
|
|
14
|
+
/** Teto de threads no painel: cada uma custa um retrato. */
|
|
15
|
+
export const TETO_DE_THREADS = 40
|
|
16
|
+
/** As operacoes que so leem; as outras tem efeito e nunca rodam pela Torre (D4). */
|
|
17
|
+
export const LEITURAS: readonly string[] = ['thread.status', 'thread.observe', 'git.status']
|
|
18
|
+
/** A ordem e o rotulo de cada operacao do retrato (o enum `operation` do nucleo). */
|
|
19
|
+
export const ROTULO_DA_ACAO: Readonly<Record<string, string>> = {
|
|
20
|
+
'thread.status': 'Estado',
|
|
21
|
+
'thread.observe': 'Observar',
|
|
22
|
+
'git.status': 'Git',
|
|
23
|
+
'hitl.decide': 'Decidir',
|
|
24
|
+
'retry': 'Retomar',
|
|
25
|
+
'phase': 'Fase seguinte',
|
|
26
|
+
'verify': 'Verificar',
|
|
27
|
+
'ship': 'Entregar',
|
|
28
|
+
'master': 'Nota do MASTER',
|
|
29
|
+
'git.commit': 'Commit',
|
|
30
|
+
'creation': 'Criação',
|
|
31
|
+
}
|
|
32
|
+
const ORDEM_DAS_ACOES = Object.keys(ROTULO_DA_ACAO)
|
|
33
|
+
const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
34
|
+
const SECOES_LIDAS = ['threads', 'sessions', 'blockers', 'hitl', 'nextActions']
|
|
35
|
+
const TAG_DO_MODO: Readonly<Record<string, string>> = { classic: '#Classic', maestro: '#Maestro', auto: '#Auto', fast: '#Fast' }
|
|
36
|
+
const MARCA_DO_ESTADO: Readonly<Record<TorreEstado, string>> = {
|
|
37
|
+
'espera-voce': '!', 'parada': 'x', 'andando': '>', 'quieta': '-', 'sem-leitura': '?',
|
|
38
|
+
}
|
|
39
|
+
/** Os valores do nucleo que chegam ao dono, em portugues; o que a tabela nao conhece aparece cru. */
|
|
40
|
+
const EM_PORTUGUES: Readonly<Record<string, string>> = {
|
|
41
|
+
delivered: 'entregue', incomplete: 'incompleta', pending: 'pendente', recorded: 'gravado',
|
|
42
|
+
'ratificacao-pendente': 'ratificação pendente', unknown: 'desconhecido', working: 'trabalhando', running: 'rodando',
|
|
43
|
+
blocked: 'bloqueada', completed: 'concluída', done: 'concluída', stopped: 'parada', exited: 'encerrada',
|
|
44
|
+
conflict: 'em conflito', unavailable: 'indisponível', stale: 'velha', available: 'disponível',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function emPortugues(valor: string): string {
|
|
48
|
+
return Object.prototype.hasOwnProperty.call(EM_PORTUGUES, valor) ? EM_PORTUGUES[valor] as string : valor
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type Item = { id?: unknown; title?: unknown; status?: unknown; refs?: unknown; facts?: unknown; action?: unknown }
|
|
52
|
+
type Retrato = { sections?: Record<string, { state?: unknown; items?: unknown } | undefined> } | null | undefined
|
|
53
|
+
|
|
54
|
+
function texto(v: unknown): string | null {
|
|
55
|
+
return typeof v === 'string' && v.trim() !== '' ? v.trim() : null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function itens(retrato: Retrato, secao: string): Item[] {
|
|
59
|
+
const s = retrato?.sections?.[secao]
|
|
60
|
+
return Array.isArray(s?.items) ? (s?.items as unknown[]).filter((i): i is Item => !!i && typeof i === 'object') : []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fatos(item: Item): Record<string, unknown> {
|
|
64
|
+
return item.facts && typeof item.facts === 'object' ? item.facts as Record<string, unknown> : {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** O item e desta thread pela referencia `thread`, nunca pelo titulo. */
|
|
68
|
+
function daThread(item: Item, thread: string): boolean {
|
|
69
|
+
return Array.isArray(item.refs) && item.refs.some(r => !!r && typeof r === 'object' &&
|
|
70
|
+
(r as { kind?: unknown }).kind === 'thread' && (r as { id?: unknown }).id === thread)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** As threads abertas da consulta `thread.list` da ponte (`ork thread list --json`), em ordem e com teto. */
|
|
74
|
+
export function abertasDaLista(lista: unknown, teto = TETO_DE_THREADS): { ids: string[]; omitidas: number } {
|
|
75
|
+
const threads = (lista as { threads?: unknown } | null)?.threads
|
|
76
|
+
if (!Array.isArray(threads)) throw new Error('a lista das threads veio sem o campo threads')
|
|
77
|
+
const ids = [...new Set(threads.filter(t => !!t && typeof t === 'object' && typeof t.id === 'string' &&
|
|
78
|
+
ID_DE_THREAD.test(t.id) && t.status !== 'fechada').map(t => t.id as string))].sort()
|
|
79
|
+
return { ids: ids.slice(0, teto), omitidas: Math.max(0, ids.length - teto) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** O estado da linha (D6): pedido ou bloqueio do dono, outro bloqueio, alguem conduzindo, ou nada. */
|
|
83
|
+
export function estadoDaLinha(l: Pick<TorreLinha, 'pedidos' | 'bloqueio' | 'conducao'>, lido: boolean): TorreEstado {
|
|
84
|
+
if (!lido) return 'sem-leitura'
|
|
85
|
+
if (l.pedidos.length > 0 || (l.bloqueio !== null && MOTIVOS_DO_DONO.includes(l.bloqueio.motivo))) return 'espera-voce'
|
|
86
|
+
if (l.bloqueio !== null) return 'parada'
|
|
87
|
+
return l.conducao !== null ? 'andando' : 'quieta'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A linha de uma thread a partir do retrato dela. Sem retrato (a leitura falhou), a linha diz que nao leu e
|
|
92
|
+
* por que, sem inventar fase nem estado.
|
|
93
|
+
*/
|
|
94
|
+
export function linhaDaThread(thread: string, retrato: unknown,
|
|
95
|
+
meta: { fonte: TorreLinha['fonte']; lidoEm: number | null; erro?: string }): TorreLinha {
|
|
96
|
+
const r = retrato as Retrato
|
|
97
|
+
const lido = !!r && typeof r === 'object' && !meta.erro
|
|
98
|
+
const linha = itens(r, 'threads').find(i => i.id === thread)
|
|
99
|
+
const f = linha ? fatos(linha) : {}
|
|
100
|
+
const sessoesDoRetrato = itens(r, 'sessions').filter(i => daThread(i, thread))
|
|
101
|
+
const conducao = sessoesDoRetrato.map(i => texto(fatos(i).conduction)).find((c): c is string => c !== null) ?? null
|
|
102
|
+
const sessoes: TorreSessao[] = sessoesDoRetrato.filter(i => typeof i.id === 'string' && !i.id.startsWith('conducao:'))
|
|
103
|
+
.map(i => ({ id: i.id as string, rotulo: texto(i.title) ?? '', estado: texto(i.status) ?? 'unknown',
|
|
104
|
+
conduz: texto(fatos(i).conduction) !== null, confirmada: fatos(i).nativeConfirmed === true }))
|
|
105
|
+
const gate = itens(r, 'blockers').find(i => daThread(i, thread) && i.id === `gate:${thread}`)
|
|
106
|
+
const motivo = gate ? texto(fatos(gate).reason) ?? texto(gate.title) : null
|
|
107
|
+
const pedidos: TorrePedido[] = itens(r, 'hitl').filter(i => daThread(i, thread) && i.status !== 'stale' && typeof i.id === 'string')
|
|
108
|
+
.map(i => ({ id: i.id as string, texto: texto(i.title) ?? '', prazo: texto(fatos(i).deadlineLocal) ?? '',
|
|
109
|
+
recomendacao: texto(fatos(i).recommendation) ?? '' }))
|
|
110
|
+
const acoes: TorreAcao[] = itens(r, 'nextActions').filter(i => daThread(i, thread))
|
|
111
|
+
.map(i => i.action as { operation?: unknown; available?: unknown } | undefined)
|
|
112
|
+
.filter((a): a is { operation: string; available: true } => !!a && a.available === true && typeof a.operation === 'string')
|
|
113
|
+
.map(a => ({ operacao: a.operation }))
|
|
114
|
+
.filter((a, i, todas) => todas.findIndex(b => b.operacao === a.operacao) === i)
|
|
115
|
+
.sort((a, b) => posicao(a.operacao) - posicao(b.operacao))
|
|
116
|
+
const lacunas = lido
|
|
117
|
+
? [...(linha ? [] : ['a thread não veio no retrato']),
|
|
118
|
+
...SECOES_LIDAS.filter(s => ['unavailable', 'conflict', 'stale'].includes(String(r?.sections?.[s]?.state)))
|
|
119
|
+
.map(s => `${s}: ${emPortugues(String(r?.sections?.[s]?.state))}`)]
|
|
120
|
+
: [meta.erro ?? 'o retrato não veio']
|
|
121
|
+
const base = {
|
|
122
|
+
thread,
|
|
123
|
+
fase: texto(f.phase),
|
|
124
|
+
modo: texto(f.mode),
|
|
125
|
+
conducao,
|
|
126
|
+
bloqueio: motivo ? { motivo, frase: fraseDoMotivo(motivo) } : null,
|
|
127
|
+
pedidos,
|
|
128
|
+
sessoes,
|
|
129
|
+
entrega: texto(itens(r, 'ship').find(i => daThread(i, thread))?.status),
|
|
130
|
+
master: texto(itens(r, 'master').find(i => daThread(i, thread))?.status),
|
|
131
|
+
acoes,
|
|
132
|
+
fonte: meta.fonte,
|
|
133
|
+
lidoEm: meta.lidoEm,
|
|
134
|
+
lacunas,
|
|
135
|
+
}
|
|
136
|
+
return { ...base, estado: estadoDaLinha(base, lido) }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function posicao(operacao: string): number {
|
|
140
|
+
const i = ORDEM_DAS_ACOES.indexOf(operacao)
|
|
141
|
+
return i < 0 ? ORDEM_DAS_ACOES.length : i
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A acao com efeito que o estado da thread pede (D15); commit e criacao nunca sao acao do dono. */
|
|
145
|
+
function pedidaPeloEstado(operacao: string, l: TorreLinha): boolean {
|
|
146
|
+
const motivo = l.bloqueio?.motivo ?? ''
|
|
147
|
+
switch (operacao) {
|
|
148
|
+
case 'hitl.decide': return l.pedidos.length > 0 || motivo === 'human.pending'
|
|
149
|
+
case 'retry': return l.estado === 'parada'
|
|
150
|
+
case 'phase': return l.estado === 'quieta' && l.fase !== 'MASTER'
|
|
151
|
+
case 'verify': return /^(claims|verify)\./.test(motivo)
|
|
152
|
+
case 'ship': return l.fase === 'SHIP' && l.entrega !== 'delivered' && l.estado !== 'andando'
|
|
153
|
+
case 'master': return (l.entrega === 'delivered' || l.fase === 'MASTER') && l.master !== 'recorded'
|
|
154
|
+
default: return false
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* As acoes que a Torre oferece ao dono (D15). O `available` do retrato so diz que a ferramenta existe no
|
|
160
|
+
* servidor (core/src/maestro-actions.ts): a leitura aparece sempre que disponivel, e a acao com efeito so
|
|
161
|
+
* quando, alem de disponivel, o estado da thread a pede.
|
|
162
|
+
*/
|
|
163
|
+
export function acoesRelevantes(l: TorreLinha): TorreAcao[] {
|
|
164
|
+
if (l.estado === 'sem-leitura') return []
|
|
165
|
+
return l.acoes.filter(a => LEITURAS.includes(a.operacao) || pedidaPeloEstado(a.operacao, l))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** As linhas na ordem do estado (D6) e, dentro dele, pelo id. */
|
|
169
|
+
export function ordenarLinhas(linhas: readonly TorreLinha[]): TorreLinha[] {
|
|
170
|
+
return [...linhas].sort((a, b) => ORDEM_DO_ESTADO.indexOf(a.estado) - ORDEM_DO_ESTADO.indexOf(b.estado) ||
|
|
171
|
+
(a.thread < b.thread ? -1 : a.thread > b.thread ? 1 : 0))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** O trilho das seis fases: feita, atual, por vir; fase fora do ciclo deixa o trilho apagado. */
|
|
175
|
+
export function trilho(fase: string | null): string {
|
|
176
|
+
const i = fase === null ? -1 : FASES.indexOf(fase as TorreFase)
|
|
177
|
+
return FASES.map((_, j) => (i < 0 ? '·' : j < i ? '●' : j === i ? '◉' : '○')).join('')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function modoComTag(modo: string | null): string {
|
|
181
|
+
if (modo === null) return ''
|
|
182
|
+
return TAG_DO_MODO[modo] ?? `#${modo}`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Idade curta: segundos, minutos ou horas. */
|
|
186
|
+
export function idade(ms: number): string {
|
|
187
|
+
const t = Math.max(0, ms)
|
|
188
|
+
if (t < 60000) return `${Math.floor(t / 1000)} s`
|
|
189
|
+
if (t < 3600000) return `${Math.floor(t / 60000)} min`
|
|
190
|
+
return `${Math.floor(t / 3600000)} h`
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** O placar de uma linha no topo do painel. */
|
|
194
|
+
export function placar(q: TorreQuadro, agora: number): string {
|
|
195
|
+
if (q.lidoEm === null) return q.erro ? `Torre: não li o núcleo (${q.erro})` : 'Torre: lendo o núcleo…'
|
|
196
|
+
const lido = `lido há ${idade(agora - q.lidoEm)}`
|
|
197
|
+
const total = q.linhas.length + q.omitidas
|
|
198
|
+
if (total === 0) return `nenhuma thread aberta nesta máquina · ${lido}`
|
|
199
|
+
const conta = (e: TorreEstado) => q.linhas.filter(l => l.estado === e).length
|
|
200
|
+
const partes = [total === 1 ? '1 thread aberta' : `${total} threads abertas`]
|
|
201
|
+
const espera = conta('espera-voce')
|
|
202
|
+
const paradas = conta('parada')
|
|
203
|
+
const andando = conta('andando')
|
|
204
|
+
const semLeitura = conta('sem-leitura')
|
|
205
|
+
if (espera) partes.push(espera === 1 ? '1 espera você' : `${espera} esperam você`)
|
|
206
|
+
if (paradas) partes.push(paradas === 1 ? '1 parada' : `${paradas} paradas`)
|
|
207
|
+
if (andando) partes.push(`${andando} andando`)
|
|
208
|
+
if (semLeitura) partes.push(semLeitura === 1 ? '1 sem leitura' : `${semLeitura} sem leitura`)
|
|
209
|
+
partes.push(lido)
|
|
210
|
+
if (q.erro) partes.push(`a lista não foi relida: ${q.erro}`)
|
|
211
|
+
return partes.join(' · ')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** A primeira linha de cada thread: estado, id, trilho, fase e modo. */
|
|
215
|
+
export function cabecalhoDaLinha(l: TorreLinha): string {
|
|
216
|
+
return [MARCA_DO_ESTADO[l.estado], l.thread, `${trilho(l.fase)} ${l.fase ?? '?'}`, modoComTag(l.modo)]
|
|
217
|
+
.filter(p => p !== '').join(' ')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Uma linha de texto com o que ela mostra ao dono, quando e algo que o Telegram tambem avisaria (D10). */
|
|
221
|
+
export type ItemDoPainel = { texto: string; mostrado?: TorreMostrado }
|
|
222
|
+
|
|
223
|
+
/** As linhas de baixo: o que espera o dono, o bloqueio, quem conduz, as acoes pedidas e as lacunas. */
|
|
224
|
+
export function detalhesDaLinha(l: TorreLinha): ItemDoPainel[] {
|
|
225
|
+
const saida: ItemDoPainel[] = []
|
|
226
|
+
for (const p of l.pedidos) {
|
|
227
|
+
const texto = `espera você: ${p.texto || p.id}`
|
|
228
|
+
saida.push({ texto: `${texto}${p.prazo ? ` (prazo ${p.prazo})` : ''}`, mostrado: { chave: `hitl:${p.id}`, thread: l.thread, texto: `${l.thread}: ${texto}` } })
|
|
229
|
+
}
|
|
230
|
+
if (l.bloqueio) {
|
|
231
|
+
const doDono = MOTIVOS_DO_DONO.includes(l.bloqueio.motivo)
|
|
232
|
+
saida.push({ texto: `${doDono ? 'espera você' : 'parada'}: ${l.bloqueio.frase} (${l.bloqueio.motivo})`,
|
|
233
|
+
mostrado: { chave: `bloqueio:${l.thread}:${l.bloqueio.motivo}`, thread: l.thread, texto: `${l.thread}: ${l.bloqueio.frase}` } })
|
|
234
|
+
}
|
|
235
|
+
if (l.estado !== 'sem-leitura') saida.push({ texto: l.conducao ?? 'ninguém conduz agora' })
|
|
236
|
+
const efeitos = acoesRelevantes(l).filter(a => !LEITURAS.includes(a.operacao)).map(a => ROTULO_DA_ACAO[a.operacao] ?? a.operacao)
|
|
237
|
+
if (efeitos.length) saida.push({ texto: `ações: ${efeitos.join(' · ')}` })
|
|
238
|
+
if (l.lacunas.length) saida.push({ texto: `lacuna: ${l.lacunas.join('; ')}` })
|
|
239
|
+
return saida
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Tudo que o quadro mostraria e o Telegram tambem avisa: cada bloqueio e cada pedido, com a chave. */
|
|
243
|
+
export function mostradosDoQuadro(q: TorreQuadro): TorreMostrado[] {
|
|
244
|
+
return q.linhas.flatMap(l => detalhesDaLinha(l).flatMap(d => (d.mostrado ? [d.mostrado] : [])))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** A linha da rede no painel (D11): nao lida, lida com a idade e as lacunas, ou o erro da ultima tentativa. */
|
|
248
|
+
export function placarDaRede(rede: TorreRede | null | undefined, agora: number): string {
|
|
249
|
+
if (!rede || rede.lidoEm === null) return rede?.erro ? `rede: não lida (${rede.erro})` : 'rede: não lida'
|
|
250
|
+
const n = rede.linhas.length
|
|
251
|
+
const maquinas = rede.maquinas === 1 ? '1 outra máquina' : `${rede.maquinas} outras máquinas`
|
|
252
|
+
const partes = [`rede: ${n === 0 ? 'nenhuma thread ativa' : n === 1 ? '1 thread' : `${n} threads`} em ${maquinas}, lida há ${idade(agora - rede.lidoEm)}`]
|
|
253
|
+
if (rede.lacunas) partes.push(rede.lacunas === 1 ? '1 lacuna' : `${rede.lacunas} lacunas`)
|
|
254
|
+
if (rede.erro) partes.push(`a última leitura falhou: ${rede.erro}`)
|
|
255
|
+
return partes.join(' · ')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** As linhas da rede que nao sao threads abertas aqui: a local vence. */
|
|
259
|
+
export function linhasDaRedeVisiveis(rede: TorreRede | null | undefined, q: TorreQuadro): TorreLinhaDaRede[] {
|
|
260
|
+
const locais = new Set(q.linhas.map(l => l.thread))
|
|
261
|
+
return (rede?.linhas ?? []).filter(l => !locais.has(l.thread))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** A linha de uma thread de outra maquina: a marca, o id, o trilho, a fase, o modo, a maquina e a batida. */
|
|
265
|
+
export function cabecalhoDaRede(l: TorreLinhaDaRede): string {
|
|
266
|
+
const batida = l.semBatida ? 'sem batida' : l.idadeMin === null ? 'batida ilegível' : `batida há ${l.idadeMin} min`
|
|
267
|
+
return [l.esperaVoce ? '!' : '~', l.thread, `${trilho(l.fase)} ${l.fase}`, l.modo, `@${l.maquina}, ${batida}`].filter(p => p !== '').join(' ')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Corta o texto a largura, quando ha uma, para a linha ocupar uma linha so da tela. */
|
|
271
|
+
export function cortar(texto: string, largura?: number): string {
|
|
272
|
+
if (largura === undefined || largura < 2 || texto.length <= largura) return texto
|
|
273
|
+
return `${texto.slice(0, largura - 1)}…`
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Uma linha da tela do painel, na ordem do desenho; cada uma ocupa uma linha so. */
|
|
277
|
+
export type LinhaDoPainel =
|
|
278
|
+
| { tipo: 'placar' | 'rede' | 'omitidas'; texto: string; thread?: undefined; mostrado?: undefined }
|
|
279
|
+
| { tipo: 'cabecalho' | 'rede-cabecalho' | 'rede-pergunta'; texto: string; thread: string; mostrado?: undefined }
|
|
280
|
+
| { tipo: 'detalhe'; texto: string; thread: string; indice: number; mostrado?: TorreMostrado }
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* As linhas da tela do painel, na ordem em que o desenho as poe: o placar, a linha da rede, cada thread desta
|
|
284
|
+
* maquina (a primeira linha e as de baixo), o que ficou alem do teto e as threads das outras maquinas. A
|
|
285
|
+
* janela de rolagem conta estas linhas, uma a uma.
|
|
286
|
+
*/
|
|
287
|
+
export function linhasDoPainel(q: TorreQuadro, opcoes: { agora: number; rede?: TorreRede | null }): LinhaDoPainel[] {
|
|
288
|
+
const saida: LinhaDoPainel[] = [{ tipo: 'placar', texto: placar(q, opcoes.agora) }, { tipo: 'rede', texto: placarDaRede(opcoes.rede, opcoes.agora) }]
|
|
289
|
+
for (const l of q.linhas) {
|
|
290
|
+
saida.push({ tipo: 'cabecalho', texto: cabecalhoDaLinha(l), thread: l.thread })
|
|
291
|
+
detalhesDaLinha(l).forEach((d, indice) => saida.push({ tipo: 'detalhe', texto: ` ${d.texto}`, thread: l.thread, indice,
|
|
292
|
+
...(d.mostrado ? { mostrado: d.mostrado } : {}) }))
|
|
293
|
+
}
|
|
294
|
+
if (q.omitidas > 0) saida.push({ tipo: 'omitidas', texto: `+${q.omitidas} threads abertas além do teto de ${TETO_DE_THREADS}` })
|
|
295
|
+
for (const l of linhasDaRedeVisiveis(opcoes.rede, q)) {
|
|
296
|
+
saida.push({ tipo: 'rede-cabecalho', texto: cabecalhoDaRede(l), thread: l.thread })
|
|
297
|
+
if (l.esperaVoce) saida.push({ tipo: 'rede-pergunta', texto: ` espera você: ${l.pergunta ?? 'pausa humana valendo'}`, thread: l.thread })
|
|
298
|
+
}
|
|
299
|
+
return saida
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* O que esta dentro da janela de rolagem (D10): as linhas `primeira` em diante da tela, de `offset` ate
|
|
304
|
+
* `offset + bodyRows`. Sem janela, nada conta como visto: o registro nunca diz que o dono viu o que talvez
|
|
305
|
+
* nao estivesse na tela.
|
|
306
|
+
*/
|
|
307
|
+
export function mostradosVisiveis(linhas: ReadonlyArray<{ texto: string; mostrado?: TorreMostrado | undefined }>, primeira: number,
|
|
308
|
+
janela: { offset: number; bodyRows: number } | undefined): TorreMostrado[] {
|
|
309
|
+
if (!janela || !(janela.bodyRows > 0)) return []
|
|
310
|
+
const fim = janela.offset + janela.bodyRows
|
|
311
|
+
return linhas.flatMap((l, i) => (l.mostrado && primeira + i >= janela.offset && primeira + i < fim ? [l.mostrado] : []))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* A arvore do painel com os elementos que a superficie resolveu (`$.ui.resolve(e)` no register.tsx), uma
|
|
316
|
+
* linha da tela por elemento. A primeira linha de cada thread desta maquina e um botao que abre o detalhe dela
|
|
317
|
+
* (T1.2); as threads das outras maquinas vem depois, sem botao (T1.3).
|
|
318
|
+
*/
|
|
319
|
+
export function desenharPainel(el: { Box: any; Text: any; Button: any }, q: TorreQuadro,
|
|
320
|
+
opcoes: { agora: number; largura?: number; abrir?: (thread: string) => void; rede?: TorreRede | null; lerRede?: () => void }) {
|
|
321
|
+
const { Box, Text, Button } = el
|
|
322
|
+
return (
|
|
323
|
+
<Box flexDirection="column">
|
|
324
|
+
{linhasDoPainel(q, opcoes).map(l => {
|
|
325
|
+
if (l.tipo === 'placar') return <Text key="placar" bold wrap="truncate">{l.texto}</Text>
|
|
326
|
+
if (l.tipo === 'rede') {
|
|
327
|
+
return (
|
|
328
|
+
<Box key="rede" flexDirection="row">
|
|
329
|
+
<Text key="placar-da-rede" dimColor wrap="truncate">{l.texto}</Text>
|
|
330
|
+
<Button key="ler-rede" label="Ler a rede" onPress={() => opcoes.lerRede?.()} />
|
|
331
|
+
</Box>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
if (l.tipo === 'cabecalho') {
|
|
335
|
+
return <Button key={`abrir:${l.thread}`} plain onPress={() => opcoes.abrir?.(l.thread)}>{cortar(l.texto, opcoes.largura)}</Button>
|
|
336
|
+
}
|
|
337
|
+
if (l.tipo === 'detalhe') return <Text key={`detalhe:${l.thread}:${l.indice}`} dimColor wrap="truncate">{l.texto}</Text>
|
|
338
|
+
if (l.tipo === 'omitidas') return <Text key="omitidas" dimColor wrap="truncate">{l.texto}</Text>
|
|
339
|
+
if (l.tipo === 'rede-cabecalho') return <Text key={`rede-cabecalho:${l.thread}`} wrap="truncate">{l.texto}</Text>
|
|
340
|
+
return <Text key={`rede-pergunta:${l.thread ?? ''}`} dimColor wrap="truncate">{l.texto}</Text>
|
|
341
|
+
})}
|
|
342
|
+
</Box>
|
|
343
|
+
)
|
|
344
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// A rede e a linha da faixa na Torre de Controle (RM-059, fatia F1, T1.3; D9 e D11 da thread). O retrato do
|
|
2
|
+
// nucleo nao le outras maquinas (`notConsulted` do `ork_maestro`): as threads delas vem do panorama da rede
|
|
3
|
+
// (`ork_network_roadmap`, contrato `ork.network-roadmap/v1`), lido so quando o dono pede ou, com o painel a
|
|
4
|
+
// vista, no maximo a cada 10 min. Lacuna nunca vira "vazio", e thread que esta aberta aqui nao se repete. A
|
|
5
|
+
// linha da faixa resume o quadro numa linha so, e a ponte a desenha. Modulo puro.
|
|
6
|
+
import type { TorreLinhaDaRede, TorreQuadro, TorreRede } from '../types'
|
|
7
|
+
|
|
8
|
+
export const CONTRATO_DA_REDE = 'ork.network-roadmap/v1'
|
|
9
|
+
/** Com o painel a vista, a rede e relida no maximo neste intervalo; o botao le na hora. */
|
|
10
|
+
export const PERIODO_DA_REDE_MS = 600000
|
|
11
|
+
export const TETO_DE_LINHAS_DA_REDE = 30
|
|
12
|
+
/** O mesmo teto que a ponte aplica ao texto de cada linha da faixa. */
|
|
13
|
+
export const TETO_DO_TEXTO_DA_FAIXA = 200
|
|
14
|
+
|
|
15
|
+
const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
16
|
+
|
|
17
|
+
function lista(v: unknown): Array<Record<string, unknown>> {
|
|
18
|
+
return Array.isArray(v) ? v.filter((x): x is Record<string, unknown> => !!x && typeof x === 'object') : []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* As threads ativas das outras maquinas, lidas do panorama da rede que o `ork_network_roadmap` devolve
|
|
23
|
+
* (`{ texto, panorama }`). Entra so maquina que nao e esta e cujas threads foram lidas; thread aberta aqui
|
|
24
|
+
* (`locais`) ou repetida em outra maquina entra uma vez so. As lacunas do panorama sao contadas, nunca
|
|
25
|
+
* escondidas.
|
|
26
|
+
*/
|
|
27
|
+
export function redeDoPanorama(resposta: unknown, locais: readonly string[], agora: number): TorreRede {
|
|
28
|
+
const panorama = (resposta as { panorama?: unknown } | null)?.panorama as Record<string, unknown> | null | undefined
|
|
29
|
+
if (!panorama || panorama.contrato !== CONTRATO_DA_REDE || !Array.isArray(panorama.projetos)) {
|
|
30
|
+
throw new Error('o panorama da rede veio fora do contrato')
|
|
31
|
+
}
|
|
32
|
+
const vistas = new Set(locais)
|
|
33
|
+
const linhas: TorreLinhaDaRede[] = []
|
|
34
|
+
let maquinas = 0
|
|
35
|
+
let lacunas = lista(panorama.lacunas).length
|
|
36
|
+
for (const projeto of lista(panorama.projetos)) {
|
|
37
|
+
lacunas += lista(projeto.lacunas).length
|
|
38
|
+
for (const m of lista(projeto.maquinas)) {
|
|
39
|
+
if (m.estaMaquina === true || m.threadsLidas !== true) continue
|
|
40
|
+
maquinas++
|
|
41
|
+
for (const t of lista(m.ativas)) {
|
|
42
|
+
if (typeof t.id !== 'string' || !ID_DE_THREAD.test(t.id) || vistas.has(t.id) || t.status === 'fechada') continue
|
|
43
|
+
vistas.add(t.id)
|
|
44
|
+
linhas.push({
|
|
45
|
+
thread: t.id,
|
|
46
|
+
maquina: typeof m.maquina === 'string' ? m.maquina : '?',
|
|
47
|
+
fase: typeof t.fase === 'string' ? t.fase : '?',
|
|
48
|
+
modo: typeof t.modo === 'string' ? t.modo : '',
|
|
49
|
+
esperaVoce: t.esperaVoce === true,
|
|
50
|
+
pergunta: typeof t.pergunta === 'string' && t.pergunta.trim() !== '' ? t.pergunta.trim() : null,
|
|
51
|
+
idadeMin: typeof m.idadeMin === 'number' && Number.isFinite(m.idadeMin) ? Math.max(0, Math.round(m.idadeMin)) : null,
|
|
52
|
+
semBatida: m.semBatida === true,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
linhas.sort((a, b) => Number(b.esperaVoce) - Number(a.esperaVoce) || (a.maquina < b.maquina ? -1 : a.maquina > b.maquina ? 1 : 0) ||
|
|
58
|
+
(a.thread < b.thread ? -1 : a.thread > b.thread ? 1 : 0))
|
|
59
|
+
return { lidoEm: agora, linhas: linhas.slice(0, TETO_DE_LINHAS_DA_REDE), maquinas, lacunas, erro: null }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A leitura que falhou guarda o que havia e diz o motivo. */
|
|
63
|
+
export function redeComErro(anterior: TorreRede | null | undefined, erro: string): TorreRede {
|
|
64
|
+
return { lidoEm: anterior?.lidoEm ?? null, linhas: anterior?.linhas ?? [], maquinas: anterior?.maquinas ?? 0,
|
|
65
|
+
lacunas: anterior?.lacunas ?? 0, erro }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cortar(texto: string, teto: number): string {
|
|
69
|
+
const limpo = texto.replace(/\s+/g, ' ').trim()
|
|
70
|
+
return limpo.length <= teto ? limpo : `${limpo.slice(0, teto - 1)}…`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A linha da faixa (D9): as threads abertas e o que pede acao primeiro (quem espera o dono e as paradas,
|
|
75
|
+
* com a primeira de cada), a rede quando lida e o aviso de lista velha. Sem quadro lido, nenhuma linha (a
|
|
76
|
+
* faixa fica com os outros).
|
|
77
|
+
*/
|
|
78
|
+
export function textoDaFaixa(q: TorreQuadro, rede: TorreRede | null): string | undefined {
|
|
79
|
+
if (q.lidoEm === null) return undefined
|
|
80
|
+
const total = q.linhas.length + q.omitidas
|
|
81
|
+
const partes = [total === 0 ? 'nenhuma thread aberta nesta máquina' : total === 1 ? '1 thread aberta' : `${total} threads abertas`]
|
|
82
|
+
const espera = q.linhas.filter(l => l.estado === 'espera-voce')
|
|
83
|
+
const paradas = q.linhas.filter(l => l.estado === 'parada')
|
|
84
|
+
const andando = q.linhas.filter(l => l.estado === 'andando').length
|
|
85
|
+
if (espera.length) partes.push(`${espera.length} ${espera.length === 1 ? 'espera' : 'esperam'} você (${espera[0]?.thread})`)
|
|
86
|
+
if (paradas.length) {
|
|
87
|
+
const p = paradas[0]
|
|
88
|
+
partes.push(`${paradas.length} ${paradas.length === 1 ? 'parada' : 'paradas'} (${p?.thread}: ${p?.bloqueio?.frase ?? '?'})`)
|
|
89
|
+
}
|
|
90
|
+
if (!espera.length && !paradas.length && andando) partes.push(`${andando} andando`)
|
|
91
|
+
if (rede && rede.lidoEm !== null) {
|
|
92
|
+
const esperaNaRede = rede.linhas.filter(l => l.esperaVoce).length
|
|
93
|
+
partes.push(`rede: ${rede.linhas.length} em outras máquinas${esperaNaRede ? `, ${esperaNaRede} espera você` : ''}`)
|
|
94
|
+
}
|
|
95
|
+
if (q.erro) partes.push('lista não relida')
|
|
96
|
+
return cortar(partes.join(' · '), TETO_DO_TEXTO_DA_FAIXA)
|
|
97
|
+
}
|