@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.
Files changed (212) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/mods/autoridade.json +56 -0
  3. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  4. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  5. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  6. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  8. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  9. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  10. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  11. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  12. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  13. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  16. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  17. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  18. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  19. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  20. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  21. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  25. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  26. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  27. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  28. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  29. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  30. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  33. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  34. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  35. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  36. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  37. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  38. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  44. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  45. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  46. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  47. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  48. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  49. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  52. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  53. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  54. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  55. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  56. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  57. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  59. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  60. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  61. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  62. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  63. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  64. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  67. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  68. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  69. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  70. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  71. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  72. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  77. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  78. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  79. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
  80. package/adapters/openclaw/README.md +2 -1
  81. package/adapters/openclaw/openclaw.plugin.json +1 -1
  82. package/adapters/openclaw/package.json +1 -1
  83. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  84. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  85. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  86. package/assets/docs-en/produto/README.md +14 -0
  87. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  88. package/assets/docs-en/roadmap/README.md +14 -0
  89. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  90. package/dist/adapters/claude-bg.js +77 -13
  91. package/dist/adapters/codex.js +10 -1
  92. package/dist/agents-md.js +24 -15
  93. package/dist/auditrun.js +17 -2
  94. package/dist/board.js +4 -3
  95. package/dist/branch-de-estado.js +23 -1
  96. package/dist/catalogo.js +33 -0
  97. package/dist/ci.js +13 -7
  98. package/dist/claims-conferir.js +342 -0
  99. package/dist/claims.js +3 -1
  100. package/dist/company-brain-client.js +36 -1
  101. package/dist/company-brain-journal.js +2 -1
  102. package/dist/company-brain-migration.js +36 -1
  103. package/dist/conducao-sentinela.js +388 -0
  104. package/dist/conducao-texto.js +12 -18
  105. package/dist/conducao.js +122 -30
  106. package/dist/conhecimento.js +490 -0
  107. package/dist/docs.js +22 -8
  108. package/dist/doctor-pulse-cron.js +182 -27
  109. package/dist/doctor-rede.js +18 -17
  110. package/dist/doctor.js +115 -111
  111. package/dist/entrega-pr.js +22 -21
  112. package/dist/escopo-escrita.js +10 -1
  113. package/dist/estado-thread.js +23 -1
  114. package/dist/experiencia-instalacao.js +45 -25
  115. package/dist/fabrica-batida.js +310 -0
  116. package/dist/fabrica-estado.js +38 -8
  117. package/dist/fabrica-publicar.js +3 -1
  118. package/dist/fila-pesada.js +444 -0
  119. package/dist/fix.js +3 -2
  120. package/dist/forja.js +1 -1
  121. package/dist/gates.js +3 -28
  122. package/dist/handoff.js +10 -6
  123. package/dist/hitl-local-receipt.js +2 -0
  124. package/dist/hitl-resumo.js +29 -3
  125. package/dist/hitl-tempo-parado.js +4 -4
  126. package/dist/hitl.js +29 -33
  127. package/dist/horario.js +14 -7
  128. package/dist/hosts.js +52 -1
  129. package/dist/impedimento.js +108 -14
  130. package/dist/index.js +623 -521
  131. package/dist/indice.js +7 -1
  132. package/dist/init.js +43 -47
  133. package/dist/instabilidade.js +144 -0
  134. package/dist/integracoes-locais.js +2 -0
  135. package/dist/intelligence-graph-cli.js +92 -21
  136. package/dist/intelligence-graph-contexto.js +56 -17
  137. package/dist/intelligence-graph-contract.js +2 -1
  138. package/dist/intelligence-graph-extract-md.js +56 -4
  139. package/dist/intelligence-graph-extract-ts.js +71 -3
  140. package/dist/intelligence-graph-query.js +66 -19
  141. package/dist/ledger.js +15 -1
  142. package/dist/liveness.js +6 -3
  143. package/dist/locale-ajuda.js +761 -0
  144. package/dist/locale-gerados.js +160 -0
  145. package/dist/locale-master.js +99 -0
  146. package/dist/locale-onboarding.js +92 -0
  147. package/dist/locale-phase.js +151 -0
  148. package/dist/locale-pulse.js +211 -0
  149. package/dist/locale-retry.js +385 -0
  150. package/dist/locale-roadmap.js +102 -0
  151. package/dist/locale-ship.js +494 -0
  152. package/dist/locale-thread.js +277 -0
  153. package/dist/locale-verify.js +125 -0
  154. package/dist/locale.js +714 -0
  155. package/dist/manifest.js +129 -3
  156. package/dist/maquina.js +23 -4
  157. package/dist/master-digest.js +6 -1
  158. package/dist/master-nota.js +4 -6
  159. package/dist/master.js +21 -24
  160. package/dist/mcp-artifacts.js +11 -2
  161. package/dist/mcp-git.js +23 -6
  162. package/dist/mcp-grafo.js +7 -4
  163. package/dist/mcp-server.js +8 -2
  164. package/dist/mcp-ship.js +3 -1
  165. package/dist/mcp-verify.js +6 -1
  166. package/dist/modos.js +3 -6
  167. package/dist/network-roadmap.js +5 -2
  168. package/dist/noctum-arco.js +911 -0
  169. package/dist/noctum-capacidade.js +141 -0
  170. package/dist/noctum-envelope.js +810 -0
  171. package/dist/noctum-execucao.js +304 -0
  172. package/dist/noctum-fila.js +280 -0
  173. package/dist/ocupacao.js +29 -2
  174. package/dist/onboarding.js +40 -21
  175. package/dist/orkmind.js +9 -2
  176. package/dist/orquestracao.js +13 -12
  177. package/dist/parado-no-condutor.js +34 -9
  178. package/dist/phase.js +94 -39
  179. package/dist/policies.js +4 -4
  180. package/dist/portfolio.js +4 -2
  181. package/dist/postura-local.js +234 -0
  182. package/dist/preflight.js +11 -10
  183. package/dist/procedencia.js +225 -0
  184. package/dist/projeto-alvo.js +127 -4
  185. package/dist/prompts.js +6 -2
  186. package/dist/pulse-delivery.js +13 -2
  187. package/dist/pulse.js +52 -19
  188. package/dist/ratelimit.js +4 -2
  189. package/dist/redacao-saida.js +63 -0
  190. package/dist/rede-status.js +3 -2
  191. package/dist/retry.js +232 -148
  192. package/dist/roadmap-reservas.js +20 -3
  193. package/dist/roadmap-status.js +129 -33
  194. package/dist/runtime-context.js +181 -3
  195. package/dist/runtimes.js +2 -1
  196. package/dist/session-events.js +7 -3
  197. package/dist/session-watcher-claude.js +7 -1
  198. package/dist/session-watcher.js +185 -33
  199. package/dist/sessoes-contas.js +1 -1
  200. package/dist/ship-publicar.js +800 -0
  201. package/dist/ship.js +84 -81
  202. package/dist/steal.js +77 -0
  203. package/dist/thread-close.js +3 -2
  204. package/dist/thread.js +153 -82
  205. package/dist/util.js +8 -1
  206. package/dist/verify.js +141 -52
  207. package/dist/vida-da-thread.js +254 -0
  208. package/dist/worktree-local.js +165 -0
  209. package/dist/worktree.js +20 -0
  210. package/dist/yaml.js +27 -8
  211. package/package.json +1 -1
  212. 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
+ }