@orkastery/cli 0.5.1 → 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 (260) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/agents/ork-master.md +2 -0
  3. package/adapters/claude-code/commands/master.md +8 -0
  4. package/adapters/claude-code/commands/ork.md +9 -0
  5. package/adapters/claude-code/mods/autoridade.json +56 -0
  6. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  8. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  9. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  10. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  11. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  12. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  13. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  16. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  17. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  18. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  19. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  20. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  21. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  25. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  26. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  27. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  28. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  29. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  30. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  33. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  34. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  35. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  36. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  37. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  38. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  44. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  45. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  46. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  47. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  48. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  49. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  52. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  53. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  54. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  55. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  56. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  57. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  59. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  60. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  61. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  62. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  63. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  64. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  67. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  68. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  69. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  70. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  71. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  72. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  77. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  78. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  79. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  80. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  81. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  82. package/adapters/codex/skills/ork/SKILL.md +18 -3
  83. package/adapters/hermes/README.md +1 -0
  84. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  85. package/adapters/hermes/hermes.plugin.json +1 -0
  86. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  87. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +7 -6
  88. package/adapters/openclaw/README.md +26 -5
  89. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  90. package/adapters/openclaw/dist/index.js +46 -11
  91. package/adapters/openclaw/openclaw.plugin.json +5 -1
  92. package/adapters/openclaw/package.json +2 -2
  93. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  94. package/adapters/openclaw/src/index.ts +49 -11
  95. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  96. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  97. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  98. package/assets/docs-en/produto/README.md +14 -0
  99. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  100. package/assets/docs-en/roadmap/README.md +14 -0
  101. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  102. package/assets/orkmind_bridge.py +117 -9
  103. package/dist/adapters/claude-bg.js +116 -18
  104. package/dist/adapters/codex.js +14 -4
  105. package/dist/agents-md.js +24 -14
  106. package/dist/auditrun.js +22 -3
  107. package/dist/board.js +20 -17
  108. package/dist/branch-de-estado.js +88 -10
  109. package/dist/busca-semantica.js +52 -12
  110. package/dist/canarios-rm057.js +149 -0
  111. package/dist/canarios.js +2 -0
  112. package/dist/catalogo.js +33 -0
  113. package/dist/ci.js +21 -8
  114. package/dist/claims-conferir.js +342 -0
  115. package/dist/claims.js +41 -1
  116. package/dist/company-brain-client.js +36 -1
  117. package/dist/company-brain-context.js +158 -22
  118. package/dist/company-brain-journal.js +2 -1
  119. package/dist/company-brain-migration.js +36 -1
  120. package/dist/conducao-sentinela.js +388 -0
  121. package/dist/conducao-texto.js +12 -18
  122. package/dist/conducao.js +122 -30
  123. package/dist/conhecimento.js +490 -0
  124. package/dist/docs.js +70 -17
  125. package/dist/doctor-pulse-cron.js +349 -0
  126. package/dist/doctor-rede.js +150 -0
  127. package/dist/doctor.js +195 -111
  128. package/dist/entrega-pr.js +42 -29
  129. package/dist/escopo-escrita.js +10 -1
  130. package/dist/esgotamentos.js +181 -0
  131. package/dist/estado-thread.js +23 -1
  132. package/dist/experiencia-instalacao.js +45 -25
  133. package/dist/experiencia.js +12 -2
  134. package/dist/fabrica-batida.js +310 -0
  135. package/dist/fabrica-estado.js +52 -14
  136. package/dist/fabrica-publicar.js +9 -1
  137. package/dist/fechamento.js +20 -66
  138. package/dist/fila-pesada.js +444 -0
  139. package/dist/fix.js +3 -2
  140. package/dist/forja.js +31 -7
  141. package/dist/gates.js +3 -24
  142. package/dist/handoff.js +10 -6
  143. package/dist/hitl-contract.js +59 -4
  144. package/dist/hitl-curto.js +11 -6
  145. package/dist/hitl-gates.js +5 -0
  146. package/dist/hitl-local-receipt.js +2 -0
  147. package/dist/hitl-lote.js +5 -2
  148. package/dist/hitl-resumo.js +38 -3
  149. package/dist/hitl-tempo-parado.js +201 -0
  150. package/dist/hitl-texto-livre.js +5 -5
  151. package/dist/hitl.js +29 -33
  152. package/dist/horario.js +41 -7
  153. package/dist/hosts.js +104 -11
  154. package/dist/impedimento.js +299 -0
  155. package/dist/index.js +961 -542
  156. package/dist/indice-vetorial.js +87 -10
  157. package/dist/indice.js +9 -1
  158. package/dist/init.js +56 -43
  159. package/dist/instabilidade.js +144 -0
  160. package/dist/integracoes-locais.js +4 -0
  161. package/dist/intelligence-graph-cli.js +170 -16
  162. package/dist/intelligence-graph-contexto.js +254 -0
  163. package/dist/intelligence-graph-contract.js +2 -1
  164. package/dist/intelligence-graph-extract-md.js +56 -4
  165. package/dist/intelligence-graph-extract-ts.js +71 -3
  166. package/dist/intelligence-graph-parsers.js +6 -4
  167. package/dist/intelligence-graph-query.js +66 -19
  168. package/dist/leases.js +664 -87
  169. package/dist/ledger-stats.js +15 -1
  170. package/dist/ledger.js +18 -1
  171. package/dist/licoes.js +1 -0
  172. package/dist/liveness.js +6 -3
  173. package/dist/locale-ajuda.js +761 -0
  174. package/dist/locale-gerados.js +160 -0
  175. package/dist/locale-master.js +99 -0
  176. package/dist/locale-onboarding.js +92 -0
  177. package/dist/locale-phase.js +151 -0
  178. package/dist/locale-pulse.js +211 -0
  179. package/dist/locale-retry.js +385 -0
  180. package/dist/locale-roadmap.js +102 -0
  181. package/dist/locale-ship.js +494 -0
  182. package/dist/locale-thread.js +277 -0
  183. package/dist/locale-verify.js +125 -0
  184. package/dist/locale.js +714 -0
  185. package/dist/maestro-runtime.js +4 -1
  186. package/dist/manifest.js +149 -4
  187. package/dist/maquina.js +23 -4
  188. package/dist/master-digest.js +6 -1
  189. package/dist/master-nota.js +18 -11
  190. package/dist/master.js +145 -31
  191. package/dist/mcp-artifacts.js +43 -3
  192. package/dist/mcp-git.js +42 -8
  193. package/dist/mcp-grafo-worker.js +5 -3
  194. package/dist/mcp-grafo.js +55 -6
  195. package/dist/mcp-server.js +22 -3
  196. package/dist/mcp-ship.js +3 -1
  197. package/dist/mcp-verify.js +6 -1
  198. package/dist/memoria.js +48 -6
  199. package/dist/modos.js +3 -6
  200. package/dist/network-roadmap.js +196 -18
  201. package/dist/noctum-arco.js +911 -0
  202. package/dist/noctum-capacidade.js +141 -0
  203. package/dist/noctum-envelope.js +810 -0
  204. package/dist/noctum-execucao.js +304 -0
  205. package/dist/noctum-fila.js +280 -0
  206. package/dist/ocupacao.js +40 -5
  207. package/dist/onboarding.js +40 -21
  208. package/dist/orkmind.js +99 -7
  209. package/dist/orquestracao.js +24 -21
  210. package/dist/parado-no-condutor.js +219 -38
  211. package/dist/phase.js +160 -52
  212. package/dist/policies.js +56 -5
  213. package/dist/portfolio.js +8 -3
  214. package/dist/postura-local.js +234 -0
  215. package/dist/preflight.js +11 -10
  216. package/dist/procedencia.js +225 -0
  217. package/dist/projeto-alvo.js +132 -5
  218. package/dist/prompts.js +40 -1
  219. package/dist/prova-ativacao.js +75 -6
  220. package/dist/pulse-delivery.js +47 -4
  221. package/dist/pulse.js +98 -19
  222. package/dist/ratelimit.js +24 -6
  223. package/dist/recall.js +3 -1
  224. package/dist/redacao-saida.js +63 -0
  225. package/dist/rede-adesao.js +399 -0
  226. package/dist/rede-forja.js +289 -0
  227. package/dist/rede-projetos.js +239 -0
  228. package/dist/rede-status.js +337 -0
  229. package/dist/rede.js +980 -0
  230. package/dist/retry.js +291 -140
  231. package/dist/roadmap-reservas.js +20 -3
  232. package/dist/roadmap-status.js +150 -34
  233. package/dist/runtime-context.js +181 -3
  234. package/dist/runtime-profiles.js +22 -1
  235. package/dist/runtimes.js +2 -1
  236. package/dist/saida-segura.js +58 -0
  237. package/dist/session-events.js +7 -3
  238. package/dist/session-watcher-claude.js +120 -24
  239. package/dist/session-watcher.js +185 -33
  240. package/dist/sessoes-contas.js +187 -0
  241. package/dist/sessoes-inventario.js +6 -18
  242. package/dist/sessoes.js +83 -2
  243. package/dist/ship-publicar.js +800 -0
  244. package/dist/ship.js +157 -88
  245. package/dist/steal.js +77 -0
  246. package/dist/thread-close.js +3 -2
  247. package/dist/thread.js +299 -49
  248. package/dist/util.js +8 -1
  249. package/dist/verify.js +141 -52
  250. package/dist/vida-da-thread.js +254 -0
  251. package/dist/worktree-local.js +165 -0
  252. package/dist/worktree.js +25 -2
  253. package/dist/yaml.js +27 -8
  254. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  255. package/package.json +7 -3
  256. package/references/definition-of-done.md +1 -1
  257. package/schemas/code-artifact-graph.v1.schema.json +2 -1
  258. package/skills/README.md +1 -1
  259. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  260. package/skills/phases/master-metrics/SKILL.md +8 -3
@@ -0,0 +1,322 @@
1
+ // O painel do Radar de Colisão (RM-059, fatia F3, T3.2). Módulo puro: a grade threads × (base e threads) com a
2
+ // faixa de calor de cada par, as células do `Raster` do terminal, a tabela das outras superfícies, as linhas do
3
+ // painel com o que entra no registro do mostrado, a linha da faixa e as colisões novas que o dono ainda não viu.
4
+ // Nenhum `$` passa por aqui; o register.tsx resolve os elementos da superfície e os passa ao desenho.
5
+ import type { RadarCelula, RadarMapa, RadarThread } from '../types'
6
+ import { BASE, chaveDoPar, comandoQueResolve, contar, nomeDoLado, NOME_DA_BASE, textoDaBase, textoDosArquivos, textoSemColisao } from './colisao'
7
+
8
+ /** Largura de cada célula da matriz, em colunas do terminal. */
9
+ export const LARGURA_DA_CELULA = 3
10
+ /** Teto do rótulo de cada thread na coluna da esquerda. */
11
+ export const LARGURA_DO_ROTULO = 28
12
+
13
+ /** A faixa de calor de um par (D10 e T3.2): limpo, 1 arquivo, 2 ou 3, 4 ou mais, erro, não calculado ou sem par. */
14
+ export type Faixa = 'limpa' | 'baixa' | 'media' | 'alta' | 'erro' | 'pendente' | 'vazia' | 'diagonal'
15
+
16
+ /** As cores de cada faixa (fundo e frente em hex; null é a cor padrão da superfície) e o símbolo da célula. */
17
+ export const PALETA: Readonly<Record<Faixa, { fundo: string | null; frente: string | null }>> = {
18
+ limpa: { fundo: '#2e7d32', frente: '#ffffff' },
19
+ baixa: { fundo: '#f9a825', frente: '#000000' },
20
+ media: { fundo: '#ef6c00', frente: '#000000' },
21
+ alta: { fundo: '#c62828', frente: '#ffffff' },
22
+ erro: { fundo: '#6a1b9a', frente: '#ffffff' },
23
+ pendente: { fundo: '#546e7a', frente: '#ffffff' },
24
+ vazia: { fundo: null, frente: null },
25
+ diagonal: { fundo: null, frente: '#757575' },
26
+ }
27
+ /** A cor padrão do terminal no `Raster` (bit 24 sozinho). */
28
+ export const COR_PADRAO = 0x01000000
29
+
30
+ export type CelulaDaGrade = { faixa: Faixa; simbolo: string; par: RadarCelula | null }
31
+ export type Grade = { colunas: string[]; linhas: Array<{ thread: RadarThread; celulas: CelulaDaGrade[] }> }
32
+
33
+ /** A faixa de um par pelo estado e pelo número de arquivos em conflito. */
34
+ export function faixaDaCelula(c: RadarCelula | undefined): Faixa {
35
+ if (c === undefined) return 'vazia'
36
+ if (c.estado === 'limpa') return 'limpa'
37
+ if (c.estado === 'erro') return 'erro'
38
+ if (c.estado === 'pendente') return 'pendente'
39
+ const n = c.arquivos.length
40
+ return n >= 4 ? 'alta' : n >= 2 ? 'media' : 'baixa'
41
+ }
42
+
43
+ /** O símbolo da célula: o número de arquivos em conflito (`+` de 10 em diante), `.` limpo, `!` erro, `?` não calculado. */
44
+ export function simboloDaCelula(faixa: Faixa, c: RadarCelula | undefined): string {
45
+ if (faixa === 'diagonal') return '-'
46
+ if (faixa === 'vazia') return ' '
47
+ if (faixa === 'limpa') return '.'
48
+ if (faixa === 'erro') return '!'
49
+ if (faixa === 'pendente') return '?'
50
+ const n = c?.arquivos.length ?? 0
51
+ return n >= 10 ? '+' : String(n)
52
+ }
53
+
54
+ /** A grade do mapa: a coluna da base (`m`) e uma por thread, e uma linha por thread, com a matriz simétrica. */
55
+ export function gradeDoMapa(m: RadarMapa): Grade {
56
+ const porPar = new Map(m.celulas.map(c => [c.par, c]))
57
+ const colunas = ['m', ...m.threads.map(t => t.rotulo)]
58
+ const lados = [BASE, ...m.threads.map(x => x.id)]
59
+ const linhas = m.threads.map((t, i) => {
60
+ const celulas = lados.map((lado, j): CelulaDaGrade => {
61
+ if (j === i + 1) return { faixa: 'diagonal', simbolo: simboloDaCelula('diagonal', undefined), par: null }
62
+ const par = porPar.get(chaveDoPar(t.id, lado))
63
+ const faixa = faixaDaCelula(par)
64
+ return { faixa, simbolo: simboloDaCelula(faixa, par), par: par ?? null }
65
+ })
66
+ return { thread: t, celulas }
67
+ })
68
+ return { colunas, linhas }
69
+ }
70
+
71
+ /** Hex `#rrggbb` para a cor do `Raster` (`0x00RRGGBB`); null é a cor padrão. */
72
+ export function corDoRaster(hex: string | null): number {
73
+ if (hex === null || !/^#[0-9a-fA-F]{6}$/.test(hex)) return COR_PADRAO
74
+ return parseInt(hex.slice(1), 16)
75
+ }
76
+
77
+ const ALFABETO = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
78
+
79
+ /** Base64 padrão com preenchimento, sem depender do ambiente (o `Raster` pede as células assim). */
80
+ export function base64(bytes: Uint8Array): string {
81
+ let s = ''
82
+ for (let i = 0; i < bytes.length; i += 3) {
83
+ const a = bytes[i] ?? 0
84
+ const b = bytes[i + 1] ?? 0
85
+ const c = bytes[i + 2] ?? 0
86
+ const n = (a << 16) | (b << 8) | c
87
+ s += (ALFABETO[(n >> 18) & 63] as string) + (ALFABETO[(n >> 12) & 63] as string)
88
+ s += i + 1 < bytes.length ? ALFABETO[(n >> 6) & 63] : '='
89
+ s += i + 2 < bytes.length ? ALFABETO[n & 63] : '='
90
+ }
91
+ return s
92
+ }
93
+
94
+ /** As triplas u32 `[codePoint, frente, fundo]` em little-endian, em base64, como o `Raster` as lê. */
95
+ export function codificarCelulas(palavras: readonly number[]): string {
96
+ const bytes = new Uint8Array(palavras.length * 4)
97
+ palavras.forEach((p, i) => {
98
+ bytes[i * 4] = p & 255
99
+ bytes[i * 4 + 1] = (p >>> 8) & 255
100
+ bytes[i * 4 + 2] = (p >>> 16) & 255
101
+ bytes[i * 4 + 3] = (p >>> 24) & 255
102
+ })
103
+ return base64(bytes)
104
+ }
105
+
106
+ /**
107
+ * O `Raster` do terminal: uma linha de cabeçalho com o nome de cada coluna e uma linha por thread, cada célula com
108
+ * 3 colunas (o símbolo no meio) e o fundo da faixa de calor.
109
+ */
110
+ export function rasterDoMapa(m: RadarMapa): { columns: number; rows: number; cells: string } {
111
+ const g = gradeDoMapa(m)
112
+ const palavras: number[] = []
113
+ const celula = (texto: string, frente: number, fundo: number) => {
114
+ for (const ch of texto) palavras.push(ch.codePointAt(0) as number, frente, fundo)
115
+ }
116
+ for (const nome of g.colunas) celula(` ${nome} `, COR_PADRAO, COR_PADRAO)
117
+ for (const linha of g.linhas) {
118
+ for (const c of linha.celulas) celula(` ${c.simbolo} `, corDoRaster(PALETA[c.faixa].frente), corDoRaster(PALETA[c.faixa].fundo))
119
+ }
120
+ return { columns: g.colunas.length * LARGURA_DA_CELULA, rows: g.linhas.length + 1, cells: codificarCelulas(palavras) }
121
+ }
122
+
123
+ /** O rótulo da thread na coluna da esquerda, com a largura de todos os rótulos do mapa. */
124
+ export function rotuloDaThread(t: RadarThread, largura: number): string {
125
+ return `${t.rotulo} ${t.id} ${t.fase}`.slice(0, LARGURA_DO_ROTULO).padEnd(largura)
126
+ }
127
+
128
+ /** A largura da coluna de rótulos. */
129
+ export function larguraDosRotulos(m: RadarMapa): number {
130
+ return Math.min(LARGURA_DO_ROTULO, Math.max(0, ...m.threads.map(t => `${t.rotulo} ${t.id} ${t.fase}`.length))) + 1
131
+ }
132
+
133
+ /** O que o painel diz ter mostrado ao dono de um conflito, para o registro da ponte (M5 do dono). */
134
+ export type RadarMostrado = { chave: string; thread: string; texto: string }
135
+
136
+ export type LinhaDoPainel =
137
+ | { tipo: 'titulo' | 'base' | 'erro' | 'legenda' | 'resolve' | 'nota'; texto: string }
138
+ | { tipo: 'botao'; texto: string }
139
+ | { tipo: 'matriz'; altura: number }
140
+ | { tipo: 'conflito'; texto: string; nova: boolean; mostrado: RadarMostrado }
141
+
142
+ /** A legenda da matriz: cada símbolo que a célula pode ter. */
143
+ export const LEGENDA = `m = ${NOME_DA_BASE} · número = arquivos em conflito (+ = 10 ou mais) · . limpo · ! erro do git · ? não calculado · - a própria thread · vazio = sem branch local`
144
+
145
+ /** A idade da leitura, curta. */
146
+ export function idade(ms: number): string {
147
+ const s = Math.max(0, Math.round(ms / 1000))
148
+ if (s < 60) return `${s} s`
149
+ const min = Math.round(s / 60)
150
+ return min < 60 ? `${min} min` : `${Math.round(min / 60)} h`
151
+ }
152
+
153
+ /** O texto de um par em conflito: os dois lados e os arquivos com os leases que os cobrem. */
154
+ export function textoDoConflito(c: RadarCelula): string {
155
+ return `${nomeDoLado(c.a)} × ${nomeDoLado(c.b)}: ${textoDosArquivos(c)}`
156
+ }
157
+
158
+ /** A thread que o registro do mostrado leva: o lado que não é a base. */
159
+ function threadDoPar(c: RadarCelula): string {
160
+ return c.a === BASE ? c.b : c.a
161
+ }
162
+
163
+ /**
164
+ * As linhas do painel, uma por linha da tela (a matriz ocupa uma por thread mais o cabeçalho): título, base, erro
165
+ * da última rodada, botão Atualizar, matriz, legenda, conflitos com o comando que resolve, erros e lacunas.
166
+ */
167
+ export function linhasDoPainel(m: RadarMapa, novas: readonly string[], agora: number): LinhaDoPainel[] {
168
+ const conflitos = m.celulas.filter(c => c.estado === 'conflito')
169
+ if (m.lidoEm === null) {
170
+ const linhas: LinhaDoPainel[] = [{ tipo: 'titulo', texto: 'Radar de Colisão · ainda sem rodada' }]
171
+ if (m.erro) linhas.push({ tipo: 'erro', texto: `a rodada parou: ${m.erro}` })
172
+ linhas.push({ tipo: 'botao', texto: 'Atualizar' })
173
+ return linhas
174
+ }
175
+ const omitidas = m.omitidas > 0 ? ` (mais ${m.omitidas} fora do teto)` : ''
176
+ const comNovas = novas.length > 0 ? ` (${contar(novas.length, 'novo', 'novos')})` : ''
177
+ const linhas: LinhaDoPainel[] = [
178
+ { tipo: 'titulo', texto: `Radar de Colisão · ${contar(m.threads.length, 'thread', 'threads')}${omitidas} · ${contar(conflitos.length, 'par em conflito', 'pares em conflito')}${comNovas} · lido há ${idade(agora - m.lidoEm)}` },
179
+ { tipo: 'base', texto: `base: ${textoDaBase(m.base)}` },
180
+ ]
181
+ if (m.erro) linhas.push({ tipo: 'erro', texto: `a última rodada parou: ${m.erro}; o mapa é o da rodada anterior` })
182
+ linhas.push({ tipo: 'botao', texto: 'Atualizar' })
183
+ if (m.threads.length === 0) {
184
+ linhas.push({ tipo: 'nota', texto: 'nenhuma thread aberta para cruzar' })
185
+ return linhas
186
+ }
187
+ linhas.push({ tipo: 'matriz', altura: m.threads.length + 1 })
188
+ linhas.push({ tipo: 'legenda', texto: LEGENDA })
189
+ for (const c of conflitos) {
190
+ const texto = textoDoConflito(c)
191
+ const nova = novas.includes(c.par)
192
+ linhas.push({ tipo: 'conflito', texto: nova ? `nova: ${texto}` : texto, nova, mostrado: { chave: `radar:${c.par}`, thread: threadDoPar(c), texto } })
193
+ linhas.push({ tipo: 'resolve', texto: ` resolve: ${comandoQueResolve(c)}` })
194
+ }
195
+ if (conflitos.length === 0) linhas.push({ tipo: 'nota', texto: textoSemColisao(m).replace(/^N/, 'n') })
196
+ for (const c of m.celulas.filter(x => x.estado === 'erro')) linhas.push({ tipo: 'nota', texto: `erro em ${nomeDoLado(c.a)} × ${nomeDoLado(c.b)}: ${c.erro}` })
197
+ const pendentes = m.celulas.filter(c => c.estado === 'pendente').length
198
+ if (pendentes > 0) linhas.push({ tipo: 'nota', texto: `${pendentes === 1 ? '1 par ficou' : `${pendentes} pares ficaram`} para a próxima rodada` })
199
+ const semBranch = m.threads.filter(t => t.sha === null).map(t => t.id)
200
+ if (semBranch.length > 0) linhas.push({ tipo: 'nota', texto: `sem branch local: ${semBranch.join(', ')}` })
201
+ for (const l of m.lacunas) linhas.push({ tipo: 'nota', texto: `lacuna: ${l}` })
202
+ return linhas
203
+ }
204
+
205
+ /** Os conflitos que o mapa mostraria, com o texto do registro: o que pode ter ido ao registro e ainda vale. */
206
+ export function mostradosDoMapa(m: RadarMapa): RadarMostrado[] {
207
+ return m.celulas.filter(c => c.estado === 'conflito').map(c => ({ chave: `radar:${c.par}`, thread: threadDoPar(c), texto: textoDoConflito(c) }))
208
+ }
209
+
210
+ /**
211
+ * O pedaço da linha do conflito que coube na largura do painel: a linha é cortada (`wrap="truncate"`, com a
212
+ * reticência na última coluna), e o registro leva só o que apareceu; null quando nada do conflito apareceu.
213
+ */
214
+ export function mostradoNaLargura(l: Extract<LinhaDoPainel, { tipo: 'conflito' }>, colunas: number | undefined): RadarMostrado | null {
215
+ if (colunas === undefined || !(colunas > 0) || l.texto.length <= colunas) return l.mostrado
216
+ const prefixo = l.texto.length - l.mostrado.texto.length
217
+ const texto = l.mostrado.texto.slice(0, Math.max(0, colunas - 1 - prefixo))
218
+ return texto.trim() === '' ? null : { ...l.mostrado, texto }
219
+ }
220
+
221
+ /**
222
+ * O que está dentro da janela de rolagem: as linhas da tela de `offset` até `offset + bodyRows`, cada conflito
223
+ * só com o que coube na largura. Sem janela, nada conta como visto, porque o registro nunca diz que o dono viu
224
+ * o que talvez não estivesse na tela.
225
+ */
226
+ export function mostradosVisiveis(linhas: readonly LinhaDoPainel[], janela: { offset: number; bodyRows: number } | undefined,
227
+ colunas?: number): RadarMostrado[] {
228
+ if (!janela || !(janela.bodyRows > 0)) return []
229
+ const fim = janela.offset + janela.bodyRows
230
+ const vistos: RadarMostrado[] = []
231
+ let linha = 0
232
+ for (const l of linhas) {
233
+ const altura = l.tipo === 'matriz' ? l.altura : 1
234
+ if (l.tipo === 'conflito' && linha >= janela.offset && linha < fim) {
235
+ const visto = mostradoNaLargura(l, colunas)
236
+ if (visto) vistos.push(visto)
237
+ }
238
+ linha += altura
239
+ }
240
+ return vistos
241
+ }
242
+
243
+ /**
244
+ * As colisões novas depois de uma rodada (D6): as que continuam em conflito somadas aos pares que a rodada
245
+ * achou novos, sem repetição, na ordem do mapa.
246
+ */
247
+ export function novasDepoisDaRodada(antigas: readonly string[], novosPares: readonly string[], m: RadarMapa): string[] {
248
+ const emConflito = m.celulas.filter(c => c.estado === 'conflito').map(c => c.par)
249
+ const querida = new Set([...antigas, ...novosPares])
250
+ return emConflito.filter(par => querida.has(par))
251
+ }
252
+
253
+ /** A linha da faixa (D6): só com colisão nova ainda não vista; sem novas, a linha sai. */
254
+ export function textoDaFaixa(m: RadarMapa, novas: readonly string[]): string | undefined {
255
+ const primeira = m.celulas.find(c => c.par === novas[0] && c.estado === 'conflito')
256
+ if (!primeira) return undefined
257
+ const n = primeira.arquivos.length
258
+ const resto = novas.length > 1 ? ` e mais ${novas.length - 1}` : ''
259
+ // A ponte já põe o nome do mod antes da linha (`radar: `).
260
+ return `colisão nova entre ${nomeDoLado(primeira.a)} e ${nomeDoLado(primeira.b)} (${contar(n, 'arquivo', 'arquivos')})${resto}; /radar abre o mapa`
261
+ }
262
+
263
+ export type ElementosDoPainel = { Box: any; Text: any; Button: any; Raster?: any }
264
+
265
+ /**
266
+ * A matriz: no terminal, rótulos em `Text` e as células num `Raster`; fora dele, uma tabela de `Box` e `Text`. A
267
+ * superfície decide, não a presença do construtor: fora do terminal o `Raster` existe e vira fragmento vazio.
268
+ */
269
+ function desenharMatriz(el: ElementosDoPainel, m: RadarMapa, terminal: boolean) {
270
+ const { Box, Text, Raster } = el
271
+ const largura = larguraDosRotulos(m)
272
+ if (terminal && Raster) {
273
+ const r = rasterDoMapa(m)
274
+ return (
275
+ <Box key="matriz" flexDirection="row">
276
+ <Box key="rotulos" flexDirection="column">
277
+ <Text key="rotulo-cabecalho" dimColor wrap="truncate">{''.padEnd(largura)}</Text>
278
+ {m.threads.map(t => <Text key={`rotulo:${t.id}`} wrap="truncate">{rotuloDaThread(t, largura)}</Text>)}
279
+ </Box>
280
+ <Raster key="mapa" columns={r.columns} rows={r.rows} cells={r.cells} />
281
+ </Box>
282
+ )
283
+ }
284
+ const g = gradeDoMapa(m)
285
+ return (
286
+ <Box key="matriz" flexDirection="column">
287
+ <Box key="cabecalho" flexDirection="row">
288
+ <Text key="rotulo-cabecalho" dimColor wrap="truncate">{''.padEnd(largura)}</Text>
289
+ {g.colunas.map(nome => <Text key={`coluna:${nome}`} dimColor>{` ${nome} `}</Text>)}
290
+ </Box>
291
+ {g.linhas.map(linha => (
292
+ <Box key={`linha:${linha.thread.id}`} flexDirection="row">
293
+ <Text key={`rotulo:${linha.thread.id}`} wrap="truncate">{rotuloDaThread(linha.thread, largura)}</Text>
294
+ {linha.celulas.map((c, j) => {
295
+ const cor = PALETA[c.faixa]
296
+ return <Text key={`celula:${linha.thread.id}:${j}`} {...(cor.frente ? { color: cor.frente } : {})} {...(cor.fundo ? { backgroundColor: cor.fundo } : {})}>{` ${c.simbolo} `}</Text>
297
+ })}
298
+ </Box>
299
+ ))}
300
+ </Box>
301
+ )
302
+ }
303
+
304
+ /** A árvore do painel com os elementos que a superfície resolveu, uma linha da tela por linha do painel. */
305
+ export function desenharPainel(el: ElementosDoPainel, m: RadarMapa, linhas: readonly LinhaDoPainel[],
306
+ opcoes: { terminal: boolean; atualizar?: () => unknown }) {
307
+ const { Box, Text, Button } = el
308
+ return (
309
+ <Box flexDirection="column">
310
+ {linhas.map((l, i) => {
311
+ if (l.tipo === 'titulo') return <Text key="titulo" bold wrap="truncate">{l.texto}</Text>
312
+ if (l.tipo === 'base') return <Text key="base" dimColor wrap="truncate">{l.texto}</Text>
313
+ if (l.tipo === 'erro') return <Text key="erro" color="error" wrap="truncate">{l.texto}</Text>
314
+ if (l.tipo === 'botao') return <Button key="atualizar" label={l.texto} onPress={() => opcoes.atualizar?.()} />
315
+ if (l.tipo === 'matriz') return desenharMatriz(el, m, opcoes.terminal)
316
+ if (l.tipo === 'legenda') return <Text key="legenda" dimColor wrap="truncate">{l.texto}</Text>
317
+ if (l.tipo === 'conflito') return <Text key={`conflito:${l.mostrado.chave}`} bold={l.nova} wrap="truncate">{l.texto}</Text>
318
+ return <Text key={`${l.tipo}:${i}`} dimColor={l.tipo === 'nota'} wrap="truncate">{l.texto}</Text>
319
+ })}
320
+ </Box>
321
+ )
322
+ }