@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
@@ -151,6 +151,164 @@ Sem `tool_use_id`, cada invocação do hook recebe uma identidade de ocorrência
151
151
  do transcript não identifica uma ocorrência; eventos de um novo episódio de permissão
152
152
  podem chegar sem alteração do arquivo. Replays com `tool_use_id` continuam idempotentes.
153
153
 
154
+ ## Mods nas sessões de fase (RM-059)
155
+
156
+ Mod é plugin de *function hooks* do Claude Code 2.1.287 ou mais novo. Cada mod do Orkastery mora
157
+ em `adapters/claude-code/mods/<mod>/`, com o próprio `.claude-plugin/plugin.json`, e declara o
158
+ papel em `orkastery-mod.json`:
159
+
160
+ ```json
161
+ {"formato":"ork.mod/v1","papeis":["fase"],"claudeCodeMinimo":"2.1.287"}
162
+ ```
163
+
164
+ O papel `dono` roda na sessão de quem conduz; o papel `fase`, nas sessões de fase que o `ork`
165
+ despacha. O `ork adapter install claude-code` copia os mods com o resto do adaptador e grava cada
166
+ arquivo deles no `INSTALADO.json`. No despacho `claude-bg`, cada mod de papel `fase` entra com o
167
+ próprio `--plugin-dir`, logo depois do plugin do Orkastery, quando:
168
+
169
+ - a pasta instalada do mod tem exatamente os arquivos do recibo, com os mesmos bytes e sem link;
170
+ os tipos que o próprio Claude Code escreve em `.claude-plugin/types/` (`*.d.ts` e
171
+ `tsconfig.json`) ficam fora da conta;
172
+ - o `claude --version` atende o `claudeCodeMinimo` do mod e o 2.1.287 da API de mods.
173
+
174
+ Mod que pede versão mais nova fica fora do comando, e o despacho segue: o núcleo não depende de
175
+ mod. Mod cuja dependência ficou de fora (pela versão, ou por ser só de papel `dono`) também fica.
176
+ Declaração inválida, manifesto fora do recibo ou arquivo a mais na pasta recusa o despacho com
177
+ `runtime.context.installation`, como um byte divergente. Arquivo de mod que saiu do catálogo sai da
178
+ cópia na reinstalação: o `ork adapter install claude-code` tira o que ele mesmo instalou e ninguém
179
+ editou. Arquivo editado ou acrescentado à mão fica, e a recusa diz o caminho para apagar. A dependência (`dependencies` do
180
+ `plugin.json` do mod) entra antes de quem depende; o resto segue o nome. Os mods que foram ficam no
181
+ comando gravado no evento `phase_dispatch` do ledger da thread.
182
+
183
+ O arquivo comum da raiz de `mods/`, como a lista de autoridade que a ponte lê em `../autoridade.json`
184
+ antes da própria pasta, segue a mesma regra quando há mod de fase no despacho: fora do recibo, link
185
+ ou arquivo especial recusam, e o que saiu do catálogo sai da cópia na reinstalação.
186
+
187
+ Para tirar um mod das sessões de fase, mude o papel no `orkastery-mod.json` do catálogo, ou tire o
188
+ mod, e rode `ork adapter install claude-code` de novo.
189
+
190
+ O mod de papel `dono` não vai ao despacho: ele roda na sessão de quem conduz. Num projeto com o
191
+ adaptador instalado, a cópia dele já está em `.claude/plugins/orkastery/mods/<mod>`, com cada
192
+ arquivo registrado no `INSTALADO.json` e os arquivos comuns da raiz de `mods/` ao lado. Nesse
193
+ carregamento ninguém confere os bytes: a conferência contra o recibo roda no despacho, para mod de
194
+ fase. Carregue-a na sessão com:
195
+
196
+ ```bash
197
+ claude --plugin-dir .claude/plugins/orkastery/mods/<mod>
198
+ ```
199
+
200
+ Sem o adaptador local, cada mod tem uma entrada no marketplace próprio do repositório
201
+ (`.claude-plugin/marketplace.json`, gerado por `node core/scripts/gerar-marketplaces.cjs`), que aponta
202
+ para uma cópia gerada em `marketplaces/claude-code/mods/<mod>/`. Instalado pelo marketplace, o plugin
203
+ é só a própria pasta, então o arquivo comum da raiz de `adapters/claude-code/mods/` (a lista de
204
+ autoridade da ponte, por exemplo) vai dentro de cada cópia. A instalação é no escopo do projeto:
205
+
206
+ ```bash
207
+ claude plugin marketplace add orkastery/orkastery
208
+ claude plugin install <mod>@orkastery --scope project
209
+ ```
210
+
211
+ Os dois marketplaces se chamam `orkastery`, e o local, registrado na instalação acima a partir de
212
+ `.claude/plugins/orkastery`, não lista os mods. Não troque um pelo outro num projeto que usa o
213
+ plugin do adaptador: remover um marketplace desinstala os plugins que vieram dele (medido no
214
+ Claude Code 2.1.296), e o plugin do marketplace do repositório não traz o guard `PreToolUse` nem o
215
+ MCP do adaptador.
216
+
217
+ ### Freio de Lote (`ork-freio`)
218
+
219
+ O primeiro mod de papel `fase`. Na sessão de fase, depois de cada lote de ferramentas, ele:
220
+
221
+ - acha os arquivos que o lote mexeu: os caminhos de `Edit`, `Write`, `MultiEdit` e `NotebookEdit` e a
222
+ diferença do `git status` entre um lote e outro, que pega também a edição feita por `Bash`;
223
+ - acha as claims da thread que citam esses arquivos, pelo `arquivo` ou pelo texto do comando, e avisa
224
+ quando o lote mexeu fora dos `touch_paths` do plano;
225
+ - pede ao núcleo, em segundo plano, a conferência dessas claims com
226
+ `ork claims conferir <thread> --claims ... --json`, no sandbox do verify, uma rodada por vez;
227
+ - devolve ao modelo, no lote seguinte do laço principal, o que quebrou: o id da claim, o comando e o
228
+ fim da saída. Lote sem claim afetada passa sem texto, e a linha de status mostra as claims em risco.
229
+ O lote de um subagente conta os arquivos tocados, mas não leva a entrega nem o aviso.
230
+
231
+ O Freio nunca roda o comando de uma claim, nunca grava estado de claim e nunca para o laço: quem
232
+ verifica de verdade continua sendo o `ork verify`. Ele lê o papel e a thread pelo `$.ork` da ponte, e
233
+ as claims e o plano pelas ferramentas de leitura `ork_claims_list` e `ork_artifact_read`, nunca do
234
+ disco em `.orkastery/`. Precisa de um `ork` com `ork claims conferir` no `PATH` da sessão; sem ele, ou
235
+ sem o sandbox do verify, avisa uma vez que a conferência está indisponível e segue. Teste que cria
236
+ processo pode reprovar no sandbox e passar no `ork verify`, e o aviso diz isso junto da falha. O
237
+ resultado chega no lote seguinte: se a sessão terminar antes dele, a última conferência fica sem
238
+ entrega. Falha de claim cujo arquivo foi tocado de novo durante a conferência não é entregue, porque
239
+ uma conferência nova já foi pedida. Um `Edit` que falhou também conta como toque e pode pedir uma
240
+ conferência a mais.
241
+
242
+ ### Portão de Tarefas (`ork-portao`)
243
+
244
+ O segundo mod de papel `fase`. Os portões do quadro agem só na sessão de fase cujo bloco atual tem o GO
245
+ (pelo slug do bloco no retrato do núcleo) e cuja thread tem plano com ao menos uma tarefa `T<n>` que
246
+ serve ao quadro: com `touch_paths` (caminhos entre crases, com barra, ponto ou curinga) e um verify com um
247
+ comando só entre crases, a regra do `ork claims conferir --tarefa`. Plano sem nenhuma tarefa assim não
248
+ ganha portão, para não travar o quadro do GO. Com o portão ligado:
249
+
250
+ - **criação:** `TaskCreated` recusa a tarefa cujo assunto não traz o id de uma tarefa do plano
251
+ (`T2.1 ...`), ou cuja tarefa do plano não serve ao quadro. A recusa volta ao modelo com o motivo e as
252
+ tarefas do plano que servem;
253
+ - **conclusão:** `TaskCompleted` da tarefa registrada na criação, ou da que cita no assunto uma tarefa do
254
+ plano que serve, pede ao núcleo `ork claims conferir <thread> --tarefa T2.1 --prazo 540 --json`, no
255
+ sandbox do verify, e só deixa a tarefa fechar com o item aprovado. Reprovação volta com o comando, o
256
+ código e o fim da saída; sem veredito, recusa tipada, outra condução na thread, conferência que não roda
257
+ ou falha do próprio Portão também bloqueiam, sempre com a saída: corrigir e concluir de novo, ou devolver
258
+ a tarefa para `pending` e seguir. A tarefa registrada continua conferida mesmo com o plano ilegível na
259
+ hora, porque o núcleo lê o plano por conta própria. Uma conferência por vez: a conclusão que chega no
260
+ meio de outra é recusada na hora. Da terceira reprovação igual em diante, o texto manda deixar a tarefa
261
+ aberta;
262
+ - **teammate ocioso:** com agent teams no piloto (abaixo), `TeammateIdle` entrega ao teammate a próxima
263
+ tarefa do plano que serve, não fechou nesta sessão e cuja região está livre: nenhum lease `path:` ativo,
264
+ de nenhuma thread, cruza os `touch_paths` dela e ela não cruza as tarefas dos outros teammates. Os leases
265
+ vêm do retrato de todas as threads pela consulta `maestro` da ponte
266
+ (`ork maestro --json --section leases --offset N`), seguindo o `nextOffset` das páginas que o teto de
267
+ bytes do retrato corta, até 10 páginas, porque o servidor MCP da sessão de fase só mostra os da própria
268
+ thread. O Portão não toma nem solta lease: o `ork_git_commit` toma o `path:` de cada arquivo na hora do
269
+ commit e recusa o commit com qualquer lease no caminho. Quem fica ocioso sem concluir devolve a tarefa,
270
+ que sai da distribuição automática desta sessão. Sem a lista de leases inteira (seção indisponível,
271
+ lacuna, retrato em conflito ou mais de 10 páginas), no teto de custo ou sem tarefa livre, o teammate
272
+ fica ocioso.
273
+
274
+ O Portão nunca roda o verify por conta própria: quem verifica de verdade continua sendo o `ork verify`.
275
+ Ele lê o papel pelo `$.ork.contexto` da ponte, o estado e o bloco pelo `$.ork.retrato`, os leases pela
276
+ consulta `maestro` da ponte e o plano pelo `ork_artifact_read`, nunca do disco em `.orkastery/`. Teste que
277
+ cria processo pode reprovar no sandbox e passar no `ork verify`, e o bloqueio diz isso.
278
+
279
+ Agent teams na sessão de fase só rodam no piloto, desligado por padrão. Quem conduz liga no manifesto da
280
+ raiz:
281
+
282
+ ```yaml
283
+ runtime:
284
+ agent_teams:
285
+ ligado: true
286
+ teammates: 2 # teto de teammates vivos, de 1 a 8
287
+ custo_usd: 10 # teto do custo da sessão, como o /cost soma, até 500
288
+ ```
289
+
290
+ Com a chave ligada, o GO no bloco da fase atual e o Portão entre os mods que vão à sessão, o despacho
291
+ `claude-bg` acrescenta ao `--settings` a variável `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`, os dois tetos
292
+ (`ORK_AGENT_TEAMS_TEAMMATES` e `ORK_AGENT_TEAMS_CUSTO_USD`) e `teammateMode` `in-process`, porque a sessão
293
+ de fundo não tem `tmux`. Teto inválido vira aviso do manifesto e deixa desligado. Em toda sessão de fase,
294
+ com ou sem o portão do quadro, o Portão recusa o nascimento de teammate sem os tetos, no teto de teammates
295
+ vivos ou com o custo da sessão no teto, e no teto de custo não entrega tarefa nova.
296
+
297
+ Limites conhecidos:
298
+
299
+ - o quadro do Portão vale para a sessão: numa sessão de GO redespachada, tarefa fechada na anterior pode
300
+ ser entregue de novo ao teammate, que a encontra pronta no quadro e no git;
301
+ - o líder não recebe aviso quando um teammate devolve uma tarefa; ele a vê aberta no quadro;
302
+ - a conferência do Freio, em segundo plano, e a do Portão, na conclusão, podem rodar ao mesmo tempo na
303
+ mesma worktree; cada mod só serializa as próprias;
304
+ - os tetos dependem de o Portão carregar de verdade: o despacho decide pelo `--plugin-dir`, e se o mod não
305
+ registrar na sessão, agent teams fica ligado sem ninguém aplicando os tetos;
306
+ - se o turno for interrompido no meio da conferência, o Claude Code segue sem a resposta do gancho;
307
+ - nome de lease que o retrato redige (caminho absoluto ou texto com cara de segredo) chega irreconhecível e
308
+ não cruza nenhuma região;
309
+ - o comportamento desses eventos numa sessão `claude --bg` real com agent teams ainda não foi medido; os
310
+ testes do kit e do núcleo cobrem o mod e o despacho.
311
+
154
312
  ## A #TAG de conducao
155
313
 
156
314
  O comando `/goal` extrai o modo do proprio pedido, sem reimplementar nada:
@@ -22,6 +22,8 @@ ork phase list <thread> # o que ja aconteceu, com evidencia
22
22
  - Classe de falha vem do catalogo fixo (`ork master classes`).
23
23
  - Justificativa vazia e recusada pelo comando, e a recusa nao grava nada.
24
24
  - Entrega sem MASTER log nao aconteceu.
25
+ - Aceite por omissao so da sua thread: `ork master <thread> --aceitar-omissao`. Sem a thread, o
26
+ comando fecha tambem as entregas de outras frentes paralelas.
25
27
 
26
28
  ## Regras que valem em toda fase
27
29
 
@@ -34,6 +34,14 @@ ork master --todas
34
34
 
35
35
  4. Nos modos sem pausa de MASTER, a entrega e aceita a menos que o builder diga o contrario; o
36
36
  `ork master --todas` mostra as entregas com o indice derivado do ledger, inclusive as ja pontuadas.
37
+ Para fechar por omissao, sempre com a thread, que e so a sua:
38
+
39
+ ```bash
40
+ ork master <thread> --aceitar-omissao
41
+ ```
42
+
43
+ Sem a thread, o comando aceita TODAS as entregues do projeto, inclusive as de outras frentes
44
+ paralelas; `ork master --aceitar-omissao --dry-run` mostra quais, sem gravar.
37
45
 
38
46
  ## Regras do adaptador
39
47
 
@@ -35,6 +35,8 @@ então consulte o projeto fixado. Não abre thread nem despacho por consulta.
35
35
  Sem projeto nomeado, ofereça também o panorama da rede (RM-054): `ork network roadmap`
36
36
  (todos os projetos conhecidos, as threads de todas as máquinas, fontes, frescor e lacunas)
37
37
  ou `mcp__orkastery__ork_network_roadmap` (o projeto fixado, em todas as máquinas), mostrado como vem.
38
+ As máquinas da pessoa (RM-053) vêm de `ork network status` ou de `mcp__orkastery__ork_network_status`
39
+ (no MCP, só o projeto fixado em cada máquina), mostrado como vem: lacuna nunca é "nenhuma máquina".
38
40
  O MCP atende só o projeto fixado; `projeto` em qualquer ferramenta apenas confere esse
39
41
  projeto (`projeto.fora-do-servidor` quando é outro). Pedido de outro projeto vai ao CLI com
40
42
  `--projeto <nome>` (RM-052). Leia "Projeto consultado" e "Não lido" antes de responder.
@@ -47,6 +49,13 @@ Usabilidade HITL é prioridade máxima: tópicos, recomendação e opções rotu
47
49
  UUID interno e cancelamento preservado. A condutora usa
48
50
  `mcp__orkastery__ork_request_decision`; o filho nunca responde em nome do dono.
49
51
 
52
+ HITL de condução é seleção (RM-057): de 3 a 5 alternativas, exatamente uma com o selo
53
+ "Recomendação". Nunca peça ao dono um "confirmo" em texto livre nem que ele cole texto; o pedido
54
+ que ele colou com autorização explícita vale como instrução dele, dentro dessa autorização.
55
+ Dúvida dentro da autorização vira `ork decisao registrar` e a thread segue. Texto só quando a
56
+ fábrica não consegue seguir sozinha e o dono precisa rodar um comando no terminal: mostre o
57
+ comando exato.
58
+
50
59
  | Pedido do dono | Acao do agente |
51
60
  |---|---|
52
61
  | Demanda nova | Resolva o modo pelo nucleo, confira o preflight e abra/despache a thread correspondente. |
@@ -0,0 +1,56 @@
1
+ {
2
+ "formato": "ork.mods-autoridade/v1",
3
+ "regra": "Nenhum mod aprova gate, permissão, nota do MASTER, push ou tag pelo dono, nem fala como o dono (D5 do plano dos mods, com o nome que o dono deu a ela). A decisão autônoma registrada pelo núcleo não entra nesta lista: está em permitidos.",
4
+ "leitores": [
5
+ "core/scripts/checar-mods.cjs",
6
+ "adapters/claude-code/mods/ork-ponte/hooks/register.ts"
7
+ ],
8
+ "ganchosProibidos": [
9
+ { "id": "elicitation", "evento": "classic.Elicitation", "porque": "responderia o diálogo nativo do MCP no lugar do dono" },
10
+ { "id": "elicitation-result", "evento": "classic.ElicitationResult", "porque": "trocaria a resposta que o dono deu no diálogo nativo" },
11
+ { "id": "permission-request", "evento": "classic.PermissionRequest", "porque": "aprovaria permissão pelo dono" },
12
+ { "id": "ask-user-question", "evento": "ui.render", "componente": "AskUserQuestion", "porque": "redesenharia a pergunta feita ao dono" }
13
+ ],
14
+ "decisoesProibidas": [
15
+ { "id": "tool-check-allow", "evento": "tool.check", "decisao": "allow", "porque": "aprovaria uma chamada de ferramenta pelo dono" }
16
+ ],
17
+ "chamadasProibidas": [
18
+ { "id": "prompt-submit", "chamada": "prompt.submit", "porque": "manda texto como se fosse o dono" },
19
+ { "id": "session-append-usuario", "chamada": "session.append", "tipo": "user", "porque": "grava linha de usuário que o modelo lê como palavra do dono" }
20
+ ],
21
+ "argvProibido": [
22
+ { "id": "gate-answer", "sequencia": ["gate", "answer"], "porque": "responde gate pelo dono" },
23
+ { "id": "sessions-answer", "sequencia": ["sessions", "answer"], "porque": "entrega resposta a uma sessão como se fosse do dono" },
24
+ { "id": "pulse-responder", "sequencia": ["pulse", "responder"], "porque": "responde o resumo pelo dono" },
25
+ { "id": "master-nota", "sequencia": ["master"], "opcao": "--score", "porque": "dá ou propõe a nota do MASTER" },
26
+ { "id": "master-batch", "sequencia": ["master", "batch"], "porque": "aceita MASTER em lote pelo dono" },
27
+ { "id": "master-digest-responder", "sequencia": ["master", "digest", "responder"], "porque": "responde o digest pelo dono" },
28
+ { "id": "retry-run-por", "sequencia": ["retry", "run"], "opcao": "--por", "porque": "autoriza o retry quando o modo pausa" },
29
+ { "id": "ship-autorizar-push", "sequencia": ["ship"], "opcao": "--autorizar-push", "porque": "autoriza push pelo dono" },
30
+ { "id": "git-push", "programa": "git", "subcomando": "push", "porque": "publica sem a prova do núcleo" },
31
+ { "id": "git-tag", "programa": "git", "subcomando": "tag", "porque": "tag é ato do dono; listar tags sai por outro comando de leitura" },
32
+ { "id": "gh", "programa": "gh", "porque": "PR, merge e release ficam com o núcleo e o dono" }
33
+ ],
34
+ "ambienteProibido": [
35
+ { "id": "hitl-env", "prefixo": "ORK_HITL_", "porque": "credenciais e verificadores do ingresso humano" }
36
+ ],
37
+ "caminhosProibidos": [
38
+ { "id": "hermes-env", "trecho": ".hermes/.env", "porque": "segredos do canal do dono" }
39
+ ],
40
+ "reservadosAPonte": [
41
+ { "id": "processos", "evento": "process.*", "porque": "a guarda da ponte vigia os processos" },
42
+ { "id": "ambiente", "evento": "env.*", "porque": "a guarda vigia o ambiente, e o papel da sessão sai dele" },
43
+ { "id": "arquivos", "evento": "fs.*", "porque": "a guarda vigia leituras, e a lista de autoridade sai do disco" },
44
+ { "id": "mcp", "evento": "mcp.*", "porque": "o retrato do núcleo sai do MCP" },
45
+ { "id": "store", "evento": "store.*", "porque": "o registro do que foi mostrado ao dono fica no store da ponte" },
46
+ { "id": "estado", "evento": "state.*", "porque": "a faixa, os caminhos vigiados e o cache ficam no estado da ponte" },
47
+ { "id": "ferramentas", "evento": "tool.list", "porque": "o papel dono sai da lista de ferramentas" },
48
+ { "id": "raiz", "evento": "session.root", "porque": "o papel e a raiz do estado saem da raiz da sessão" },
49
+ { "id": "registro", "evento": "plugin.register", "porque": "só a ponte confere os mods que carregam" },
50
+ { "id": "prompt", "evento": "prompt.submit", "porque": "só a ponte decide sobre texto enviado por mod" },
51
+ { "id": "transcript", "evento": "session.append", "porque": "só a ponte decide sobre linha anexada por mod" }
52
+ ],
53
+ "permitidos": [
54
+ { "id": "decisao-autonoma", "sequencia": ["decisao", "registrar"], "porque": "decisão autônoma registrada pelo núcleo, com critério e evidência, que vai ao dono no lote seguinte com o custo de reverter (M4)" }
55
+ ]
56
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "ork-clima",
3
+ "version": "0.1.0",
4
+ "description": "Clima de Cota do Orkastery no Claude Code: prevê quando a cota da conta acaba no ritmo atual, avisa na faixa antes de a fase morrer no meio e diz qual retomada não cabe. Só lê e avisa.",
5
+ "author": {
6
+ "name": "Orkastery contributors",
7
+ "url": "https://github.com/orkastery"
8
+ },
9
+ "homepage": "https://orkastery.com/",
10
+ "repository": "https://github.com/orkastery/orkastery",
11
+ "license": "MIT",
12
+ "types": "./types/index.d.ts",
13
+ "dependencies": ["ork-ponte"]
14
+ }
@@ -0,0 +1,283 @@
1
+ // O cruzamento do Clima de Cota (RM-059, onda 1, F4; T4.2): a previsao das janelas da conta desta sessao
2
+ // contra os despachos que dependem de cota (a fila de retomada inteira, de `ork retry list --json`, e as
3
+ // threads abertas paradas por cota, no retrato do nucleo) e contra as marcas de esgotamento por conta do
4
+ // registro compartilhado (RM-040). D7, D8 e D13 a D15 da thread. Modulo puro: le o que a ponte trouxe, classifica
5
+ // e escreve; nenhum `$` passa por aqui.
6
+ import type { ClimaEstado, ClimaNaoCabe, ClimaPrevisao } from '../types'
7
+ import { duracao, emAlerta, formatarHora, rotuloDaJanela, textoDaFaixa } from './previsao'
8
+
9
+ /** Uma retomada da fila do nucleo que ainda vai despachar. */
10
+ export type ClimaRetomada = {
11
+ id: string
12
+ thread: string
13
+ /** Quando a fila libera, em ms desde a epoca; `null` quando a fila nao disse. */
14
+ liberaEm: number | null
15
+ tentativas: number | null
16
+ }
17
+
18
+ /** Uma thread aberta cujo ultimo gate reprovado foi por cota. */
19
+ export type ClimaParada = { thread: string; motivo: string }
20
+
21
+ /**
22
+ * O que o retrato trouxe dos bloqueios: as paradas, se a pagina veio cortada e quantas threads ficaram sem ledger
23
+ * (`lacunasCortadas` quando o retrato cortou a lista de lacunas e o numero e um piso).
24
+ */
25
+ export type ClimaParadas = { itens: ClimaParada[]; paginaCortada: boolean; lacunas: number; lacunasCortadas: boolean }
26
+
27
+ /** Uma marca do registro compartilhado de contas (`ork.esgotamentos/v1`), com a conta opaca. */
28
+ export type ClimaMarca = {
29
+ conta: string
30
+ runtime: string
31
+ estado: string
32
+ motivo: string
33
+ esgotadoAte: number | null
34
+ projeto: string
35
+ }
36
+
37
+ export type ClimaClasse = 'nao-cabe' | 'cabe' | 'depois-de-renovar'
38
+
39
+ export type ClimaDespacho = {
40
+ retomada: ClimaRetomada
41
+ /** Quando a retomada despacha: a hora de liberar, ou agora se ja liberou. */
42
+ quando: number
43
+ classe: ClimaClasse
44
+ /** A janela que decide a classe; `null` sem janela em alerta. */
45
+ janela: string | null
46
+ estadoDaJanela: ClimaEstado | null
47
+ acabaEm: number | null
48
+ renovaEm: number | null
49
+ /** Na classe `cabe`, a cota que sobra no ritmo atual ate o primeiro esgotamento. */
50
+ folgaMs: number | null
51
+ }
52
+
53
+ /** O que o `/clima` cruzou: `null` onde a leitura falhou, com o motivo ao lado. */
54
+ export type ClimaCruzamento = {
55
+ despachos: ClimaDespacho[] | null
56
+ erroDaFila: string | null
57
+ paradas: ClimaParadas | null
58
+ erroDoRetrato: string | null
59
+ marcas: ClimaMarca[] | null
60
+ erroDoRegistro: string | null
61
+ }
62
+
63
+ /** Os motivos de gate que sao de cota: a politica do nucleo espera a janela ou roda o perfil (D7). */
64
+ export const MOTIVOS_DE_COTA: readonly string[] = ['runtime.quota-exhausted', 'runtime.rate-limited']
65
+ export const CONTRATO_DOS_ESGOTAMENTOS = 'ork.esgotamentos/v1'
66
+ /** O que o nucleo ja sabe fazer, com os comandos da propria politica dele; o Clima nunca executa (D7). */
67
+ export const SUGESTOES: readonly string[] = [
68
+ 'esperar a janela: a retomada fica na fila até renovar, e ork retry list mostra a fila;',
69
+ 'seguir em outro perfil: ork accounts list mostra os perfis e quem está esgotado, e ork retry run <thread> segue no próximo perfil do rodízio;',
70
+ 'adiar a fase: segurar o despacho da fase seguinte até a janela renovar.',
71
+ ]
72
+
73
+ const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
74
+ const ID_DE_RETOMADA = /^R[1-9][0-9]{0,8}$/
75
+ const CONTA_OPACA = /^[a-f0-9]{12}$/
76
+ const NAO_REGISTRADO = 'nao registrado'
77
+ const ORDEM_DA_CLASSE: Readonly<Record<ClimaClasse, number>> = { 'nao-cabe': 0, 'cabe': 1, 'depois-de-renovar': 2 }
78
+
79
+ type Bruto = Record<string, unknown>
80
+
81
+ function objeto(v: unknown): Bruto | null {
82
+ return v !== null && typeof v === 'object' && !Array.isArray(v) ? (v as Bruto) : null
83
+ }
84
+
85
+ function texto(v: unknown, teto: number): string | null {
86
+ return typeof v === 'string' && v.trim() !== '' ? v.replace(/\s+/g, ' ').trim().slice(0, teto) : null
87
+ }
88
+
89
+ function instante(v: unknown): number | null {
90
+ const t = typeof v === 'string' ? Date.parse(v) : NaN
91
+ return Number.isFinite(t) ? t : null
92
+ }
93
+
94
+ /**
95
+ * As retomadas que ainda vao despachar, da fila inteira (`ork retry list --json`, D13): a fila e um JSONL so de
96
+ * acrescimo, entao a ultima gravacao de um id vence, e so o estado `aguardando` conta. `null` fora do formato.
97
+ */
98
+ export function retomadasDaFila(fila: unknown): ClimaRetomada[] | null {
99
+ if (!Array.isArray(fila)) return null
100
+ const porId = new Map<string, Bruto>()
101
+ for (const bruto of fila) {
102
+ const p = objeto(bruto)
103
+ if (p !== null && typeof p.id === 'string' && ID_DE_RETOMADA.test(p.id)) porId.set(p.id, p)
104
+ }
105
+ const itens: ClimaRetomada[] = []
106
+ for (const p of porId.values()) {
107
+ if (p.estado !== 'aguardando' || typeof p.thread !== 'string' || !ID_DE_THREAD.test(p.thread)) continue
108
+ const tentativas = p.tentativas
109
+ itens.push({
110
+ id: p.id as string, thread: p.thread, liberaEm: instante(p.liberaEm),
111
+ tentativas: typeof tentativas === 'number' && Number.isInteger(tentativas) && tentativas >= 0 ? tentativas : null,
112
+ })
113
+ }
114
+ return itens
115
+ }
116
+
117
+ /** As threads abertas, de `ork thread list --json` (que sem `--todas` so lista as nao fechadas); `null` fora do formato. */
118
+ export function threadsAbertas(lista: unknown): string[] | null {
119
+ const threads = objeto(lista)?.threads
120
+ if (!Array.isArray(threads)) return null
121
+ return threads.map(objeto).filter((t): t is Bruto => t !== null && typeof t.id === 'string' && t.status !== 'fechada')
122
+ .map(t => t.id as string)
123
+ }
124
+
125
+ function threadDasRefs(refs: unknown): string | null {
126
+ for (const r of Array.isArray(refs) ? refs : []) {
127
+ const o = objeto(r)
128
+ if (o?.kind === 'thread' && typeof o.id === 'string' && ID_DE_THREAD.test(o.id)) return o.id
129
+ }
130
+ return null
131
+ }
132
+
133
+ /**
134
+ * As threads paradas por cota, da secao `blockers` do retrato (D7), so as abertas quando a lista delas veio
135
+ * (o retrato tambem traz thread fechada). A pagina cortada e a lacuna de ledger sao ditas a parte (D13).
136
+ */
137
+ export function paradasDoRetrato(retrato: unknown, abertas: readonly string[] | null): ClimaParadas {
138
+ const s = objeto(objeto(objeto(retrato)?.sections)?.blockers)
139
+ const itens: ClimaParada[] = []
140
+ const lista = s?.items
141
+ for (const bruto of Array.isArray(lista) ? lista : []) {
142
+ const i = objeto(bruto)
143
+ const motivo = objeto(i?.facts)?.reason
144
+ if (i === null || typeof motivo !== 'string' || !MOTIVOS_DE_COTA.includes(motivo)) continue
145
+ const thread = threadDasRefs(i.refs)
146
+ if (thread === null || (abertas !== null && !abertas.includes(thread)) || itens.some(p => p.thread === thread)) continue
147
+ itens.push({ thread, motivo })
148
+ }
149
+ const proxima = objeto(s?.coverage)?.nextOffset
150
+ const gaps = Array.isArray(s?.gaps) ? (s?.gaps as unknown[]).filter((g): g is string => typeof g === 'string') : []
151
+ const lacunas = gaps.filter(g => g.startsWith('ledger.unavailable:')).length
152
+ // O retrato guarda so as primeiras lacunas e conta o resto em `maestro.messages.omitted:N` (D14).
153
+ return { itens, paginaCortada: typeof proxima === 'number', lacunas, lacunasCortadas: gaps.some(g => g.startsWith('maestro.messages.omitted:')) }
154
+ }
155
+
156
+ /** As marcas do registro de contas; `null` quando o relatorio nao e do contrato `ork.esgotamentos/v1`. */
157
+ export function marcasDoRelatorio(relatorio: unknown): ClimaMarca[] | null {
158
+ const r = objeto(relatorio)
159
+ if (r === null || r.contrato !== CONTRATO_DOS_ESGOTAMENTOS || !Array.isArray(r.marcas)) return null
160
+ const marcas: ClimaMarca[] = []
161
+ for (const bruta of r.marcas) {
162
+ const m = objeto(bruta)
163
+ if (m === null || typeof m.conta !== 'string' || !CONTA_OPACA.test(m.conta)) continue
164
+ const runtime = texto(m.runtime, 40)
165
+ const estado = texto(m.estado, 40)
166
+ if (runtime === null || estado === null) continue
167
+ marcas.push({
168
+ conta: m.conta, runtime, estado, motivo: texto(m.motivo, 80) ?? 'sem motivo',
169
+ esgotadoAte: instante(m.esgotadoAte), projeto: texto(m.projeto, 80) ?? NAO_REGISTRADO,
170
+ })
171
+ }
172
+ return marcas
173
+ }
174
+
175
+ /** O trecho sem cota de uma janela em alerta: do esgotamento (ou da leitura, se ja esgotou) ate renovar. */
176
+ function trechoSemCota(p: ClimaPrevisao): { p: ClimaPrevisao; inicio: number; fim: number } | null {
177
+ if (!emAlerta(p)) return null
178
+ const inicio = p.estado === 'esgotada' ? p.lidaEm : p.acabaEm
179
+ if (inicio === null) return null
180
+ return { p, inicio, fim: p.renovaEm ?? Number.POSITIVE_INFINITY }
181
+ }
182
+
183
+ /**
184
+ * Cada retomada contra os trechos sem cota das janelas em alerta (D7): `nao-cabe` quando despacha dentro de
185
+ * um deles (na hora exata da renovacao a janela ja renovou); `cabe`, com a folga ate o primeiro esgotamento,
186
+ * quando despacha antes; `depois-de-renovar` quando todos ja renovaram. Sem janela em alerta, `cabe` sem folga.
187
+ */
188
+ export function cruzar(previsoes: readonly ClimaPrevisao[], retomadas: readonly ClimaRetomada[], agora: number): ClimaDespacho[] {
189
+ const trechos = previsoes.map(trechoSemCota).filter((t): t is NonNullable<ReturnType<typeof trechoSemCota>> => t !== null)
190
+ const despacho = (r: ClimaRetomada, quando: number, classe: ClimaClasse, t: (typeof trechos)[number] | undefined,
191
+ folgaMs: number | null): ClimaDespacho => ({
192
+ retomada: r, quando, classe, janela: t?.p.kind ?? null, estadoDaJanela: t?.p.estado ?? null,
193
+ acabaEm: t?.inicio ?? null, renovaEm: t?.p.renovaEm ?? null, folgaMs,
194
+ })
195
+ return retomadas.map(r => {
196
+ const quando = Math.max(agora, r.liberaEm ?? agora)
197
+ const dentro = trechos.filter(t => quando >= t.inicio && quando < t.fim)
198
+ if (dentro.length > 0) return despacho(r, quando, 'nao-cabe', dentro.reduce((a, b) => (b.fim > a.fim ? b : a)), null)
199
+ const antes = trechos.filter(t => quando < t.inicio)
200
+ if (antes.length > 0) {
201
+ const primeiro = antes.reduce((a, b) => (b.inicio < a.inicio ? b : a))
202
+ return despacho(r, quando, 'cabe', primeiro, primeiro.inicio - quando)
203
+ }
204
+ if (trechos.length > 0) return despacho(r, quando, 'depois-de-renovar', trechos.reduce((a, b) => (b.fim > a.fim ? b : a)), null)
205
+ return despacho(r, quando, 'cabe', undefined, null)
206
+ }).sort((a, b) => ORDEM_DA_CLASSE[a.classe] - ORDEM_DA_CLASSE[b.classe] || a.quando - b.quando)
207
+ }
208
+
209
+ /** As threads das retomadas que nao cabem, sem repetir, na ordem do cruzamento. */
210
+ export function threadsQueNaoCabem(despachos: readonly ClimaDespacho[]): string[] {
211
+ const threads: string[] = []
212
+ for (const d of despachos) if (d.classe === 'nao-cabe' && !threads.includes(d.retomada.thread)) threads.push(d.retomada.thread)
213
+ return threads
214
+ }
215
+
216
+ /** Uma retomada no `/clima`. */
217
+ export function textoDoDespacho(d: ClimaDespacho, fuso: string, agora: number): string {
218
+ const volta = d.quando <= agora ? 'volta agora' : `volta ${formatarHora(d.quando, fuso, agora)}`
219
+ const quem = `${d.retomada.id} da ${d.retomada.thread} ${volta}`
220
+ const janela = d.janela === null ? 'a janela' : `a janela de ${rotuloDaJanela(d.janela)}`
221
+ if (d.classe === 'nao-cabe') {
222
+ const acaba = d.estadoDaJanela === 'esgotada' || d.acabaEm === null ? 'já esgotou' : `acaba ~${formatarHora(d.acabaEm, fuso, agora)}`
223
+ const renova = d.renovaEm === null ? 'não tem hora de renovar' : `renova ${formatarHora(d.renovaEm, fuso, agora)}`
224
+ return `${quem} e não cabe: ${janela} ${acaba} e ${renova}.`
225
+ }
226
+ if (d.classe === 'depois-de-renovar') return `${quem}, depois de ${janela} renovar.`
227
+ if (d.folgaMs !== null) return `${quem}, com ~${duracao(d.folgaMs)} de cota no ritmo atual.`
228
+ return `${quem}.`
229
+ }
230
+
231
+ /** As retomadas que nao cabiam, como o estado guardou; a entrada fora do formato fica de fora (D15). */
232
+ export function naoCabemGuardados(valor: unknown): ClimaNaoCabe[] {
233
+ if (!Array.isArray(valor)) return []
234
+ return valor.filter((n): n is ClimaNaoCabe => !!n && typeof n.id === 'string' && typeof n.thread === 'string'
235
+ && typeof n.texto === 'string' && typeof n.registrada === 'boolean')
236
+ }
237
+
238
+ /** O que vai ao registro do mostrado por retomada que nao cabe: so o que nao muda a cada leitura (D13). */
239
+ export function textoDoNaoCabe(d: ClimaDespacho, fuso: string, agora: number): string {
240
+ const janela = d.janela === null ? 'na janela' : `na janela de ${rotuloDaJanela(d.janela)}`
241
+ const renova = d.renovaEm === null ? ', que não tem hora de renovar' : `, que renova ${formatarHora(d.renovaEm, fuso, agora)}`
242
+ return `${d.retomada.id} da ${d.retomada.thread} não cabe ${janela}${renova}.`
243
+ }
244
+
245
+ /** O resumo que o `/clima` registra como mostrado (D8): a linha da faixa, ou que nada pede atencao. */
246
+ export function resumoDoClima(previsoes: readonly ClimaPrevisao[], naoCabem: readonly string[], fuso: string, agora: number): string {
247
+ return textoDaFaixa(previsoes, fuso, agora, naoCabem) ?? 'nenhuma janela pede atenção'
248
+ }
249
+
250
+ export function textoDaParada(p: ClimaParada): string {
251
+ return `${p.thread} parada por ${p.motivo}.`
252
+ }
253
+
254
+ export function textoDaMarca(m: ClimaMarca, fuso: string, agora: number): string {
255
+ const prazo = m.esgotadoAte === null ? 'sem prazo' : `até ${formatarHora(m.esgotadoAte, fuso, agora)}`
256
+ const onde = m.projeto === NAO_REGISTRADO ? 'em projeto não registrado' : `em ${m.projeto}`
257
+ return `conta ${m.conta} (${m.runtime}): ${m.estado} ${prazo}, visto ${onde} (${m.motivo}).`
258
+ }
259
+
260
+ /** As linhas do cruzamento no `/clima`: fila, paradas, contas do registro e, se algo aperta, as sugestoes. */
261
+ export function linhasDoCruzamento(c: ClimaCruzamento, previsoes: readonly ClimaPrevisao[], fuso: string, agora: number): string[] {
262
+ const linhas = ['Despachos que dependem da cota (fila de retomada do núcleo):']
263
+ if (c.despachos === null) linhas.push(`- Fila de retomada indisponível (${c.erroDaFila ?? 'fora do formato'}).`)
264
+ else if (c.despachos.length === 0) linhas.push('- Nenhuma retomada na fila.')
265
+ else for (const d of c.despachos) linhas.push(`- ${textoDoDespacho(d, fuso, agora)}`)
266
+ linhas.push('Threads abertas paradas por cota (retrato do núcleo):')
267
+ if (c.paradas === null) linhas.push(`- Não deu para conferir as paradas (${c.erroDoRetrato ?? 'sem resposta'}).`)
268
+ else {
269
+ if (c.paradas.itens.length === 0) linhas.push('- Nenhuma.')
270
+ for (const p of c.paradas.itens) linhas.push(`- ${textoDaParada(p)}`)
271
+ if (c.paradas.paginaCortada) linhas.push('- Os bloqueios vieram em mais de uma página; o Clima leu a primeira.')
272
+ if (c.paradas.lacunas > 0) {
273
+ linhas.push(`- O retrato não leu o ledger de ${c.paradas.lacunasCortadas ? 'pelo menos ' : ''}${c.paradas.lacunas} thread(s).`)
274
+ }
275
+ }
276
+ linhas.push('Contas no registro do núcleo (ork accounts esgotamentos):')
277
+ if (c.marcas === null) linhas.push(`- Registro de contas indisponível (${c.erroDoRegistro ?? 'fora do contrato ork.esgotamentos/v1'}).`)
278
+ else if (c.marcas.length === 0) linhas.push('- Nenhuma conta marcada no registro.')
279
+ else for (const m of c.marcas) linhas.push(`- ${textoDaMarca(m, fuso, agora)}`)
280
+ const aperta = previsoes.some(emAlerta) || (c.despachos ?? []).some(d => d.classe === 'nao-cabe') || (c.paradas?.itens.length ?? 0) > 0
281
+ if (aperta) linhas.push('O que o núcleo já sabe fazer:', ...SUGESTOES.map(s => `- ${s}`))
282
+ return linhas
283
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "modules": ["./register.ts"]
3
+ }