@orkastery/cli 0.2.0

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/adapters/README.md +22 -0
  4. package/adapters/claude-code/.claude-plugin/marketplace.json +15 -0
  5. package/adapters/claude-code/.claude-plugin/plugin.json +31 -0
  6. package/adapters/claude-code/README.md +102 -0
  7. package/adapters/claude-code/agents/ork-check.md +32 -0
  8. package/adapters/claude-code/agents/ork-go.md +32 -0
  9. package/adapters/claude-code/agents/ork-goal.md +33 -0
  10. package/adapters/claude-code/agents/ork-master.md +32 -0
  11. package/adapters/claude-code/agents/ork-plan.md +32 -0
  12. package/adapters/claude-code/agents/ork-ship.md +32 -0
  13. package/adapters/claude-code/commands/check.md +45 -0
  14. package/adapters/claude-code/commands/go.md +46 -0
  15. package/adapters/claude-code/commands/goal.md +54 -0
  16. package/adapters/claude-code/commands/master.md +42 -0
  17. package/adapters/claude-code/commands/ork.md +37 -0
  18. package/adapters/claude-code/commands/plan.md +47 -0
  19. package/adapters/claude-code/commands/ship.md +45 -0
  20. package/adapters/claude-code/hooks/hooks.json +17 -0
  21. package/adapters/claude-code/hooks/ork-guard.js +130 -0
  22. package/adapters/hermes/README.md +45 -0
  23. package/adapters/hermes/bin/ork-abrir-thread.sh +25 -0
  24. package/adapters/hermes/hermes.plugin.json +21 -0
  25. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +116 -0
  26. package/adapters/openclaw/README.md +63 -0
  27. package/adapters/openclaw/bin/ork-abrir-thread.sh +24 -0
  28. package/adapters/openclaw/openclaw.plugin.json +138 -0
  29. package/dist/adapters/claude-bg.js +308 -0
  30. package/dist/auditoria.js +848 -0
  31. package/dist/auditrun.js +976 -0
  32. package/dist/board.js +314 -0
  33. package/dist/canarios.js +271 -0
  34. package/dist/catalogo.js +124 -0
  35. package/dist/ciclos.js +156 -0
  36. package/dist/claims.js +226 -0
  37. package/dist/divida.js +374 -0
  38. package/dist/doctor.js +246 -0
  39. package/dist/evalrunner.js +458 -0
  40. package/dist/fix.js +450 -0
  41. package/dist/gates.js +92 -0
  42. package/dist/handoff.js +521 -0
  43. package/dist/hosts.js +331 -0
  44. package/dist/index.js +1910 -0
  45. package/dist/init.js +231 -0
  46. package/dist/leases.js +508 -0
  47. package/dist/ledger.js +138 -0
  48. package/dist/manifest.js +255 -0
  49. package/dist/master.js +453 -0
  50. package/dist/memoria.js +773 -0
  51. package/dist/modos.js +220 -0
  52. package/dist/orkmind.js +487 -0
  53. package/dist/orquestracao.js +559 -0
  54. package/dist/phase.js +572 -0
  55. package/dist/policies.js +176 -0
  56. package/dist/prompts.js +406 -0
  57. package/dist/ratelimit.js +179 -0
  58. package/dist/recall.js +252 -0
  59. package/dist/retry.js +917 -0
  60. package/dist/sandbox.js +94 -0
  61. package/dist/sessoes.js +104 -0
  62. package/dist/ship.js +551 -0
  63. package/dist/slug.js +100 -0
  64. package/dist/superficie.js +1347 -0
  65. package/dist/thread.js +333 -0
  66. package/dist/tokens.js +228 -0
  67. package/dist/types.js +20 -0
  68. package/dist/util.js +156 -0
  69. package/dist/verify.js +262 -0
  70. package/dist/worktree.js +576 -0
  71. package/dist/yaml.js +112 -0
  72. package/eval/README.md +85 -0
  73. package/eval/casos/check-quality.json +103 -0
  74. package/eval/casos/code-reviewer.json +102 -0
  75. package/eval/casos/decision-triage.json +103 -0
  76. package/eval/casos/go-implementation.json +103 -0
  77. package/eval/casos/goal-definition.json +103 -0
  78. package/eval/casos/master-metrics.json +103 -0
  79. package/eval/casos/narrative-guardian.json +163 -0
  80. package/eval/casos/orkastery-bootstrap.json +102 -0
  81. package/eval/casos/plan-specification.json +102 -0
  82. package/eval/casos/roadmap-keeper.json +103 -0
  83. package/eval/casos/scope-check-capability-map.json +83 -0
  84. package/eval/casos/security-auditor.json +102 -0
  85. package/eval/casos/ship-release.json +122 -0
  86. package/eval/casos/test-engineer.json +122 -0
  87. package/eval/casos/thread-state.json +82 -0
  88. package/eval/casos/thread-tracing.json +83 -0
  89. package/eval/casos/web-performance-auditor.json +102 -0
  90. package/eval/fixtures/b0-slug-e-modos/caso.json +56 -0
  91. package/eval/fixtures/b2-master-log/caso.json +91 -0
  92. package/eval/fixtures/fx-concurrency/caso.json +15 -0
  93. package/eval/fixtures/fx-hallucination/caso.json +12 -0
  94. package/eval/fixtures/fx-happy/caso.json +16 -0
  95. package/eval/fixtures/fx-schema-drift/caso.json +15 -0
  96. package/eval/fixtures/fx-stale-base/caso.json +12 -0
  97. package/eval/fixtures/fx-wiki-destroy/caso.json +16 -0
  98. package/eval/fixtures/superficie-de-rede/README.md +22 -0
  99. package/eval/fixtures/superficie-de-rede/api-express.js +30 -0
  100. package/eval/fixtures/superficie-de-rede/api_fastapi.py +17 -0
  101. package/eval/fixtures/superficie-de-rede/api_gin.go +18 -0
  102. package/package.json +56 -0
  103. package/references/README.md +25 -0
  104. package/references/code-review-axes.md +82 -0
  105. package/references/definition-of-done.md +74 -0
  106. package/references/performance-checklist.md +53 -0
  107. package/references/security-checklist.md +101 -0
  108. package/references/testing-patterns.md +51 -0
  109. package/skills/README.md +43 -0
  110. package/skills/core/orkastery-bootstrap/SKILL.md +89 -0
  111. package/skills/core/thread-state/SKILL.md +86 -0
  112. package/skills/governance/decision-triage/SKILL.md +65 -0
  113. package/skills/governance/narrative-guardian/SKILL.md +66 -0
  114. package/skills/governance/roadmap-keeper/SKILL.md +60 -0
  115. package/skills/governance/scope-check-capability-map/SKILL.md +61 -0
  116. package/skills/observability/thread-tracing/SKILL.md +63 -0
  117. package/skills/phases/check-quality/SKILL.md +87 -0
  118. package/skills/phases/go-implementation/SKILL.md +70 -0
  119. package/skills/phases/goal-definition/SKILL.md +69 -0
  120. package/skills/phases/master-metrics/SKILL.md +65 -0
  121. package/skills/phases/plan-specification/SKILL.md +67 -0
  122. package/skills/phases/ship-release/SKILL.md +68 -0
  123. package/skills/reviewers/code-reviewer/SKILL.md +61 -0
  124. package/skills/reviewers/security-auditor/SKILL.md +68 -0
  125. package/skills/reviewers/test-engineer/SKILL.md +66 -0
  126. package/skills/reviewers/web-performance-auditor/SKILL.md +63 -0
package/dist/retry.js ADDED
@@ -0,0 +1,917 @@
1
+ "use strict";
2
+ /**
3
+ * Politica de retry TIPADA e retomada automatica (bloco B3).
4
+ *
5
+ * O B1 deu ao `ork` o MOTIVO tipado de cada reprovacao; o B2 deu a thread um estado
6
+ * retomavel em disco. O B3 e a camada que liga os dois: para cada motivo tipado existe
7
+ * UMA acao de retry deterministica, igual em todos os modos de conducao.
8
+ *
9
+ * Tres regras mandam aqui, e nenhuma delas e afrouxavel por modo:
10
+ *
11
+ * 1. **Custo nunca reexecuta.** `cost.violation` (despacho que seria redirecionado
12
+ * para provider pago) e o unico motivo sem retry automatico nenhum. Reexecutar
13
+ * violacao de custo e gastar de novo, e a licao dos US$ 16 esta no manifesto.
14
+ * 2. **O modo afrouxa a PAUSA, nunca a VERIFICACAO.** A politica e identica em
15
+ * `#Look` e em `#Auto`. O que muda e a AUTORIZACAO: num bloco de loop que pausa,
16
+ * a acao espera `ork gate approve`; num bloco sem pausa (`#Maestro`, `#Auto`) o
17
+ * `ork` executa sozinho e grava a decisao autonoma no ledger.
18
+ * 3. **O limite de escalacao pausa QUALQUER modo.** Estouradas as tentativas do
19
+ * manifesto pelo MESMO motivo na MESMA fase, a acao vira `escalar-humano`,
20
+ * inclusive em `#Auto`. E a invariante de escalacao tipada da secao 3.5.
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.POLITICA_DE_RETRY = exports.ESCADA_DE_ESFORCO = void 0;
57
+ exports.proximoEsforco = proximoEsforco;
58
+ exports.politicaDoMotivo = politicaDoMotivo;
59
+ exports.podeReexecutar = podeReexecutar;
60
+ exports.tabelaDaPolitica = tabelaDaPolitica;
61
+ exports.ultimaReprovacao = ultimaReprovacao;
62
+ exports.tentativasFeitas = tentativasFeitas;
63
+ exports.blocoPausa = blocoPausa;
64
+ exports.planejarRetry = planejarRetry;
65
+ exports.escalarEsforco = escalarEsforco;
66
+ exports.redespachar = redespachar;
67
+ exports.playbookDaRetomada = playbookDaRetomada;
68
+ exports.retomarPedido = retomarPedido;
69
+ exports.retomarFila = retomarFila;
70
+ exports.retomarPorId = retomarPorId;
71
+ exports.cancelarPedido = cancelarPedido;
72
+ exports.executarRetry = executarRetry;
73
+ exports.textoDoPlano = textoDoPlano;
74
+ exports.textoDoRetry = textoDoRetry;
75
+ exports.textoDaRetomada = textoDaRetomada;
76
+ const fs = __importStar(require("node:fs"));
77
+ const path = __importStar(require("node:path"));
78
+ const adapter = __importStar(require("./adapters/claude-bg"));
79
+ const fix_1 = require("./fix");
80
+ const gates_1 = require("./gates");
81
+ const ledger_1 = require("./ledger");
82
+ const phase_1 = require("./phase");
83
+ const slug_1 = require("./slug");
84
+ const policies_1 = require("./policies");
85
+ const ratelimit_1 = require("./ratelimit");
86
+ const thread_1 = require("./thread");
87
+ const tokens_1 = require("./tokens");
88
+ const util_1 = require("./util");
89
+ const worktree_1 = require("./worktree");
90
+ /**
91
+ * Escada de esforco da 2a tentativa em diante.
92
+ *
93
+ * Subir esforco e de graca na assinatura; trocar de provider nao e. Por isso a escada
94
+ * so anda dentro do runtime que ja esta configurado, e nunca vira "tenta no pago".
95
+ */
96
+ exports.ESCADA_DE_ESFORCO = ['eco', 'low', 'medium', 'high', 'xhigh'];
97
+ /** O proximo degrau de esforco, ou o mesmo quando ja esta no topo (sem inventar degrau). */
98
+ function proximoEsforco(atual) {
99
+ const i = exports.ESCADA_DE_ESFORCO.indexOf(atual.trim().toLowerCase());
100
+ if (i < 0)
101
+ return atual;
102
+ return exports.ESCADA_DE_ESFORCO[Math.min(i + 1, exports.ESCADA_DE_ESFORCO.length - 1)];
103
+ }
104
+ /**
105
+ * A politica de retry, motivo tipado a motivo tipado.
106
+ *
107
+ * O mapa e TOTAL sobre `MotivoGate` de proposito: motivo novo no catalogo do gate nao
108
+ * compila sem decidir aqui o que se faz com ele. Silencio nao e politica.
109
+ */
110
+ exports.POLITICA_DE_RETRY = {
111
+ 'artifact.missing': {
112
+ motivo: 'artifact.missing',
113
+ acao: 'corrigir-dirigido',
114
+ automatica: true,
115
+ porque: 'o artefato exigido pela fase nao existe: a fase precisa de spec dirigida do que gravar, nao de repeticao cega do mesmo prompt',
116
+ correcao: 'ork fix open <thread> abre a rodada com a spec exata',
117
+ },
118
+ 'claims.failed': {
119
+ motivo: 'claims.failed',
120
+ acao: 'corrigir-dirigido',
121
+ automatica: true,
122
+ porque: 'a alegacao reprovou na reexecucao no HEAD real: reexecutar a fase igual reproduz a mesma mentira; o que resolve e GO-FIX com a claim e o comando exatos',
123
+ correcao: 'ork fix open <thread> e depois ork fix reverify <thread>',
124
+ },
125
+ 'claims.unverifiable': {
126
+ motivo: 'claims.unverifiable',
127
+ acao: 'corrigir-dirigido',
128
+ automatica: true,
129
+ porque: 'alegacao sem comando de verificacao vira correcao tipo A: anexar o comando (ou retirar a alegacao com motivo) e uma linha, nao um GO inteiro',
130
+ correcao: 'ork claims verificar <thread> <claim> --comando "<comando>"',
131
+ },
132
+ 'policy.violation': {
133
+ motivo: 'policy.violation',
134
+ acao: 'escalar-humano',
135
+ automatica: false,
136
+ porque: 'policy com severidade block reprova em qualquer modo: reexecutar sozinho seria a maquina revogando a policy do manifesto',
137
+ correcao: 'corrija a violacao no pedido/manifesto; policy block nao se resolve por repeticao',
138
+ },
139
+ 'cost.violation': {
140
+ motivo: 'cost.violation',
141
+ acao: 'sem-retry',
142
+ automatica: false,
143
+ porque: 'violacao de CUSTO nunca recebe retry automatico: cada reexecucao gastaria de novo pelo provider pago (a licao dos US$ 16 e regra de codigo, nao lembrete)',
144
+ correcao: 'tire do ambiente a variavel que redireciona o despacho e rode a fase de novo a mao, com o gasto sob os olhos de um humano',
145
+ },
146
+ 'verify.regression': {
147
+ motivo: 'verify.regression',
148
+ acao: 'corrigir-dirigido',
149
+ automatica: true,
150
+ porque: 'o comando passava na baseline e falha agora: o defeito e desta thread e tem alvo conhecido, entao ele vira spec de GO-FIX tipo B',
151
+ correcao: 'ork fix open <thread> (a reexecucao seguinte e completa)',
152
+ },
153
+ 'verify.failed': {
154
+ motivo: 'verify.failed',
155
+ acao: 'corrigir-dirigido',
156
+ automatica: true,
157
+ porque: 'o comando falha sem baseline para separar regressao de divida: a correcao dirigida conserta ou grava a baseline, mas nao chuta de quem e a culpa',
158
+ correcao: 'ork fix open <thread>, ou ork verify <thread> --baseline antes do proximo GO',
159
+ },
160
+ 'runtime.unavailable': {
161
+ motivo: 'runtime.unavailable',
162
+ acao: 'reexecutar',
163
+ automatica: true,
164
+ porque: 'falha de infraestrutura no despacho nao e falha de conteudo: o MESMO prompt, com o mesmo sha256, e redespachado sem alterar uma virgula',
165
+ correcao: 'ork retry run <thread> redespacha o prompt ja gravado em disco',
166
+ },
167
+ 'runtime.rate-limited': {
168
+ motivo: 'runtime.rate-limited',
169
+ acao: 'esperar-janela',
170
+ automatica: true,
171
+ porque: 'o limite de uso tem HORA de volta: a fase entra na fila duravel e e retomada na janela seguinte, sem humano no meio e sem gastar por fora da assinatura',
172
+ correcao: 'ork retry list mostra a fila; ork retry resume retoma o que ja liberou',
173
+ },
174
+ 'tree.blocked': {
175
+ motivo: 'tree.blocked',
176
+ acao: 'sincronizar-worktree',
177
+ automatica: true,
178
+ porque: 'a arvore de destino andou por baixo da thread: rebasar a worktree contra a base e o passo que desbloqueia, e reexecutar antes disso so repete o conflito',
179
+ correcao: 'ork worktree sync <thread> e so entao repetir o passo',
180
+ },
181
+ 'lease.busy': {
182
+ motivo: 'lease.busy',
183
+ acao: 'reexecutar',
184
+ automatica: true,
185
+ porque: 'o lease e de outra thread e vai ser liberado: a fila ja serializa, entao a acao e tentar de novo quando chegar a vez, nunca furar a fila',
186
+ correcao: 'ork lease list mostra a fila; a vez chega sem ninguem forcar',
187
+ },
188
+ 'human.pending': {
189
+ motivo: 'human.pending',
190
+ acao: 'escalar-humano',
191
+ automatica: false,
192
+ porque: 'nao e reprovacao de verdade, e espera de autorizacao: automatizar a espera seria a maquina se autorizando',
193
+ correcao: 'ork gate approve <thread> <sobre> --por <quem>',
194
+ },
195
+ };
196
+ /** A politica de um motivo tipado. */
197
+ function politicaDoMotivo(motivo) {
198
+ return exports.POLITICA_DE_RETRY[motivo];
199
+ }
200
+ /** O motivo pode ser reexecutado automaticamente pelo `ork`? Custo nunca pode. */
201
+ function podeReexecutar(motivo) {
202
+ return exports.POLITICA_DE_RETRY[motivo].acao !== 'sem-retry';
203
+ }
204
+ /** Tabela de `ork retry policy`. */
205
+ function tabelaDaPolitica() {
206
+ const linhas = Object.values(exports.POLITICA_DE_RETRY).map((p) => [
207
+ p.motivo,
208
+ p.acao,
209
+ p.automatica ? 'sim' : 'nao',
210
+ p.porque,
211
+ ]);
212
+ return (0, util_1.tabela)(['MOTIVO TIPADO', 'ACAO DE RETRY', 'AUTOMATICA', 'POR QUE'], linhas);
213
+ }
214
+ /** A fase a que um evento de gate reprovado se refere. */
215
+ function faseDoEvento(evento, thread) {
216
+ const declarada = typeof evento.fase === 'string' ? evento.fase : null;
217
+ if (declarada && thread.fases.includes(declarada))
218
+ return declarada;
219
+ if (evento.gate === 'verify')
220
+ return 'CHECK';
221
+ if (evento.gate === 'ship')
222
+ return 'SHIP';
223
+ return thread.faseAtual;
224
+ }
225
+ /** O ultimo gate reprovado da thread, ou null quando nao ha reprovacao pendente. */
226
+ function ultimaReprovacao(raiz, threadId) {
227
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId));
228
+ for (let i = eventos.length - 1; i >= 0; i--) {
229
+ const e = eventos[i];
230
+ if (e.tipo === ledger_1.TIPOS_DE_EVENTO.gateBloqueado && typeof e.motivo === 'string')
231
+ return e;
232
+ }
233
+ return null;
234
+ }
235
+ /**
236
+ * Tentativas automaticas ja feitas pelo MESMO motivo na MESMA fase.
237
+ *
238
+ * Conta o que o `ork` REALMENTE executou (`retry_attempt` no ledger), nao o que ele
239
+ * planejou: planejar nao gasta janela nem toca no repositorio.
240
+ */
241
+ function tentativasFeitas(raiz, threadId, fase, motivo) {
242
+ return (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId)).filter((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.retryTentado && e.fase === fase && e.motivo === motivo).length;
243
+ }
244
+ /** O bloco de loop desta fase pausa para o humano neste modo? */
245
+ function blocoPausa(thread, fase) {
246
+ return (0, thread_1.blocoDaThread)(thread, fase).pausa;
247
+ }
248
+ /**
249
+ * Calcula o plano de retry SEM executar nada.
250
+ *
251
+ * Separado da execucao de proposito: `ork retry plan` precisa poder responder "o que
252
+ * voce faria agora e por que" sem gastar uma tentativa nem tocar no repositorio.
253
+ */
254
+ function planejarRetry(carregado, threadId, opcoes = {}) {
255
+ const { raiz, manifesto } = carregado;
256
+ const thread = (0, thread_1.lerThread)(raiz, threadId);
257
+ const limite = manifesto.retry.max_tentativas;
258
+ const effortBase = manifesto.runtime.effort;
259
+ const evento = opcoes.motivo ? null : ultimaReprovacao(raiz, threadId);
260
+ const motivo = opcoes.motivo ?? (evento && evento.motivo);
261
+ if (!motivo) {
262
+ return {
263
+ thread: threadId,
264
+ modo: thread.modo,
265
+ fase: opcoes.fase ?? thread.faseAtual,
266
+ motivo: null,
267
+ detalhe: 'nenhum gate reprovado no ledger desta thread',
268
+ politica: null,
269
+ acao: 'sem-retry',
270
+ tentativas: 0,
271
+ limite,
272
+ effort: effortBase,
273
+ effortAnterior: effortBase,
274
+ automatica: false,
275
+ bloqueio: 'gate.sem-reprovacao',
276
+ razao: 'nao ha o que repetir: o ledger da thread nao tem reprovacao tipada pendente',
277
+ };
278
+ }
279
+ const fase = opcoes.fase ?? (evento ? faseDoEvento(evento, thread) : thread.faseAtual);
280
+ const politica = politicaDoMotivo(motivo);
281
+ const tentativas = tentativasFeitas(raiz, threadId, fase, motivo);
282
+ const detalhe = evento && typeof evento.detalhe === 'string' ? evento.detalhe : '';
283
+ // Custo primeiro: nenhuma tentativa, nenhum degrau de esforco, nenhum modo. Nunca.
284
+ if (politica.acao === 'sem-retry') {
285
+ return {
286
+ thread: threadId,
287
+ modo: thread.modo,
288
+ fase,
289
+ motivo,
290
+ detalhe,
291
+ politica,
292
+ acao: 'sem-retry',
293
+ tentativas,
294
+ limite,
295
+ effort: effortBase,
296
+ effortAnterior: effortBase,
297
+ automatica: false,
298
+ bloqueio: 'politica.nao-automatica',
299
+ razao: politica.porque,
300
+ };
301
+ }
302
+ // Limite de escalacao: pausa QUALQUER modo, inclusive #Auto.
303
+ if (tentativas >= limite) {
304
+ return {
305
+ thread: threadId,
306
+ modo: thread.modo,
307
+ fase,
308
+ motivo,
309
+ detalhe,
310
+ politica,
311
+ acao: 'escalar-humano',
312
+ tentativas,
313
+ limite,
314
+ effort: effortBase,
315
+ effortAnterior: effortBase,
316
+ automatica: false,
317
+ bloqueio: 'escalacao.limite',
318
+ razao: `${tentativas} tentativa(s) automatica(s) por ${motivo} em ${fase}, no limite de ${limite} ` +
319
+ 'do manifesto: escalacao tipada pausa qualquer modo, inclusive #Auto',
320
+ };
321
+ }
322
+ // Da 2a tentativa em diante o esforco sobe um degrau, quando o manifesto permite.
323
+ // `tentativas` e quantas JA rodaram, entao a 1a tentativa (tentativas 0) sai no
324
+ // esforco base do manifesto e so a partir da 2a a escada anda.
325
+ const escala = manifesto.retry.escalar_esforco && tentativas >= 1;
326
+ const effortAnterior = escalarEsforco(effortBase, Math.max(0, tentativas - 1));
327
+ const effort = escala ? escalarEsforco(effortBase, tentativas) : effortBase;
328
+ const acao = escala ? 'escalar-esforco' : politica.acao;
329
+ const pausa = blocoPausa(thread, fase);
330
+ const autorizado = (opcoes.autorizadoPor ?? '').trim() !== '';
331
+ let bloqueio = null;
332
+ let automatica = politica.automatica;
333
+ let razao = politica.porque;
334
+ if (!politica.automatica) {
335
+ automatica = false;
336
+ bloqueio = 'politica.nao-automatica';
337
+ }
338
+ else if (pausa && !autorizado) {
339
+ // O bloco de loop deste modo pausa: o humano ja e parte do caminho aqui, e por isso
340
+ // a acao fica planejada e nao executada. `#Maestro` e `#Auto` nao caem neste ramo.
341
+ automatica = false;
342
+ bloqueio = 'modo.bloco-pausa';
343
+ razao =
344
+ `o bloco ${(0, thread_1.blocoDaThread)(thread, fase).fases.join('-')} pausa neste modo ` +
345
+ `(${(0, thread_1.blocoDaThread)(thread, fase).pausaSobre}): a acao ${acao} espera autorizacao humana. ` +
346
+ 'O modo afrouxa a pausa, nunca a verificacao.';
347
+ }
348
+ return {
349
+ thread: threadId,
350
+ modo: thread.modo,
351
+ fase,
352
+ motivo,
353
+ detalhe,
354
+ politica,
355
+ acao,
356
+ tentativas,
357
+ limite,
358
+ effort,
359
+ effortAnterior,
360
+ automatica,
361
+ bloqueio,
362
+ razao,
363
+ };
364
+ }
365
+ /** O esforco depois de `n` escaladas a partir do esforco base do manifesto. */
366
+ function escalarEsforco(base, degraus) {
367
+ let atual = base;
368
+ for (let i = 0; i < degraus; i++)
369
+ atual = proximoEsforco(atual);
370
+ return atual;
371
+ }
372
+ /**
373
+ * Redespacha um prompt JA GRAVADO, byte a byte.
374
+ *
375
+ * As duas guardas que fazem disto uma retomada, e nao um despacho novo:
376
+ * - o arquivo precisa existir e o sha256 precisa bater com o registrado. Prompt
377
+ * alterado entre a morte e a retomada e outro pedido, e por isso recusa;
378
+ * - as MESMAS policies do `phase.dispatch` sao reavaliadas contra o texto lido. E
379
+ * assim que a regra de custo alcanca a retomada: se o ambiente ganhou uma variavel
380
+ * de provider pago enquanto a fase esperava, a retomada recusa em vez de gastar.
381
+ */
382
+ function redespachar(carregado, thread, fase, promptRelativo, sha, opcoes = {}) {
383
+ const { raiz, manifesto } = carregado;
384
+ const absoluto = path.isAbsolute(promptRelativo)
385
+ ? promptRelativo
386
+ : path.join(raiz, promptRelativo);
387
+ const slug = opcoes.slug ?? (0, phase_1.slugDaSessao)(thread, fase);
388
+ const vazio = { slug, sessionId: null, verificada: false, sinal: null, comando: [] };
389
+ if (!fs.existsSync(absoluto)) {
390
+ return {
391
+ ...vazio,
392
+ ok: false,
393
+ motivo: 'artifact.missing',
394
+ detalhe: `o prompt gravado sumiu do disco: ${promptRelativo}`,
395
+ dryRun: opcoes.dryRun === true,
396
+ };
397
+ }
398
+ const prompt = fs.readFileSync(absoluto, 'utf8');
399
+ const shaAtual = (0, phase_1.hashDoPrompt)(prompt);
400
+ if (shaAtual !== sha) {
401
+ return {
402
+ ...vazio,
403
+ ok: false,
404
+ motivo: 'artifact.missing',
405
+ detalhe: `o prompt em ${promptRelativo} mudou desde o despacho original ` +
406
+ `(sha256 ${shaAtual.slice(0, 12)} != ${sha.slice(0, 12)}): retomada e redespachar o MESMO texto`,
407
+ dryRun: opcoes.dryRun === true,
408
+ };
409
+ }
410
+ const violacoes = (0, policies_1.bloqueantes)((0, policies_1.avaliarPolicies)(manifesto, { gate: 'phase.dispatch', prompt }));
411
+ if (violacoes.length > 0) {
412
+ const motivo = (0, policies_1.motivoDominante)(violacoes);
413
+ const detalhe = violacoes.map((v) => `${v.policy}: ${v.detalhe}`).join('; ');
414
+ (0, gates_1.registrarGateBloqueado)((0, thread_1.dirThread)(raiz, thread.id), thread.id, {
415
+ gate: 'phase.dispatch',
416
+ motivo,
417
+ modo: thread.modo,
418
+ detalhe,
419
+ correcao: violacoes.map((v) => v.correcao).join('; '),
420
+ fase,
421
+ slug,
422
+ origem: opcoes.origem ?? 'retry',
423
+ evidencia: `prompt sha256 ${sha} (retomada recusada antes de despachar)`,
424
+ });
425
+ return { ...vazio, ok: false, motivo, detalhe, dryRun: opcoes.dryRun === true };
426
+ }
427
+ const cwd = opcoes.cwd ?? thread.worktree ?? raiz;
428
+ // Mesma resolucao do `ork phase run`: a retomada carimba no ledger o par que ela de
429
+ // fato despachou, inclusive quando o pedido da fila trazia um modelo proprio.
430
+ const { model, effort } = (0, phase_1.resolverDespacho)(manifesto, {
431
+ model: opcoes.model ?? undefined,
432
+ effort: opcoes.effort ?? undefined,
433
+ });
434
+ const resultado = adapter.despachar({
435
+ prompt,
436
+ nome: slug,
437
+ cwd,
438
+ model,
439
+ effort,
440
+ dryRun: opcoes.dryRun,
441
+ });
442
+ if (opcoes.dryRun) {
443
+ return {
444
+ ok: true,
445
+ slug,
446
+ sessionId: null,
447
+ verificada: false,
448
+ motivo: null,
449
+ detalhe: 'ensaio (--dry-run): comando montado, nada despachado',
450
+ sinal: null,
451
+ comando: resultado.comando,
452
+ dryRun: true,
453
+ };
454
+ }
455
+ if (!resultado.ok || !resultado.sessionId) {
456
+ const sinal = resultado.rateLimit ?? null;
457
+ return {
458
+ ok: false,
459
+ slug,
460
+ sessionId: null,
461
+ verificada: false,
462
+ motivo: sinal ? 'runtime.rate-limited' : 'runtime.unavailable',
463
+ detalhe: resultado.erro ?? 'sem sessionId na saida do runtime',
464
+ sinal,
465
+ comando: resultado.comando,
466
+ dryRun: false,
467
+ };
468
+ }
469
+ const ctx = {
470
+ raiz,
471
+ thread,
472
+ fase,
473
+ slug,
474
+ promptPath: absoluto,
475
+ promptSha256: sha,
476
+ runtime: manifesto.runtime.adapter,
477
+ cwd,
478
+ model,
479
+ effort,
480
+ origem: opcoes.origem ?? 'retry',
481
+ };
482
+ (0, phase_1.concluirDespacho)(ctx, resultado.sessionId, resultado.verificada, resultado.comando);
483
+ return {
484
+ ok: true,
485
+ slug,
486
+ sessionId: resultado.sessionId,
487
+ verificada: resultado.verificada,
488
+ motivo: null,
489
+ detalhe: `sessao ${resultado.sessionId} despachada com o mesmo prompt sha256 ${sha.slice(0, 12)}`,
490
+ sinal: null,
491
+ comando: resultado.comando,
492
+ dryRun: false,
493
+ };
494
+ }
495
+ // ---------------------------------------------------------------------------
496
+ // Os 3 playbooks de recuperacao, em codigo.
497
+ // ---------------------------------------------------------------------------
498
+ /**
499
+ * Escolhe o playbook da retomada.
500
+ *
501
+ * Nao ha regra nova de rotacao aqui: quem decide entre a MESMA sessao e uma NOVA e o
502
+ * gate de tokens do B1 (visao, secao 3.6), com `refazer: true`, porque retomar uma fase
503
+ * morta E refazer uma fase. `escalada` e a terceira porta: o limite do manifesto.
504
+ */
505
+ function playbookDaRetomada(carregado, pedido, opcoes = {}) {
506
+ const { manifesto } = carregado;
507
+ if (pedido.tentativas >= manifesto.retry.max_tentativas) {
508
+ return {
509
+ playbook: 'escalada',
510
+ razao: `${pedido.tentativas} retomada(s) do pedido ${pedido.id} para o limite de ` +
511
+ `${manifesto.retry.max_tentativas}: escalacao tipada pausa qualquer modo, inclusive #Auto`,
512
+ slugSugerido: null,
513
+ };
514
+ }
515
+ const veredito = (0, tokens_1.gateDeTokens)(carregado, pedido.thread, {
516
+ proximo: pedido.fase,
517
+ refazer: true,
518
+ ocupacao: opcoes.ocupacao,
519
+ fonte: opcoes.fonte,
520
+ });
521
+ if (veredito.veredito === 'new-session') {
522
+ return {
523
+ playbook: 'nova-sessao',
524
+ razao: `gate de tokens: ${veredito.razao}`,
525
+ slugSugerido: veredito.slugSugerido,
526
+ };
527
+ }
528
+ return {
529
+ playbook: 'mesma-sessao',
530
+ razao: `gate de tokens: ${veredito.razao}`,
531
+ slugSugerido: null,
532
+ };
533
+ }
534
+ /** Retoma UM pedido da fila, aplicando o playbook que couber. */
535
+ function retomarPedido(carregado, pedidoOriginal, opcoes = {}) {
536
+ const { raiz } = carregado;
537
+ const quando = opcoes.quando ?? (0, util_1.agora)();
538
+ const thread = (0, thread_1.lerThread)(raiz, pedidoOriginal.thread);
539
+ const dir = (0, thread_1.dirThread)(raiz, thread.id);
540
+ let pedido = pedidoOriginal;
541
+ if (!opcoes.forcar && pedido.liberaEm > quando) {
542
+ return {
543
+ pedido,
544
+ playbook: 'mesma-sessao',
545
+ slug: pedido.slug,
546
+ despachada: false,
547
+ sessionId: null,
548
+ verificada: false,
549
+ motivo: 'runtime.rate-limited',
550
+ detalhe: `a janela ainda nao liberou (libera em ${pedido.liberaEm})`,
551
+ dryRun: opcoes.dryRun === true,
552
+ };
553
+ }
554
+ const escolha = playbookDaRetomada(carregado, pedido, opcoes);
555
+ // Playbook 3: escalada. Pausa QUALQUER modo, e o pedido sai da fila de espera.
556
+ if (escolha.playbook === 'escalada') {
557
+ (0, gates_1.registrarGateBloqueado)(dir, thread.id, {
558
+ gate: 'phase.dispatch',
559
+ motivo: 'human.pending',
560
+ modo: thread.modo,
561
+ detalhe: escolha.razao,
562
+ correcao: `ork gate approve ${thread.id} retomada --por <quem>`,
563
+ fase: pedido.fase,
564
+ slug: pedido.slug,
565
+ pedido: pedido.id,
566
+ pausaQualquerModo: true,
567
+ });
568
+ (0, ledger_1.registrar)(dir, thread.id, ledger_1.TIPOS_DE_EVENTO.retryEscalado, {
569
+ fase: pedido.fase,
570
+ motivo: 'runtime.rate-limited',
571
+ origem: 'rate-limit.fila',
572
+ pedido: pedido.id,
573
+ tentativas: pedido.tentativas,
574
+ limite: carregado.manifesto.retry.max_tentativas,
575
+ modo: thread.modo,
576
+ razao: escolha.razao,
577
+ });
578
+ pedido = (0, ratelimit_1.gravarPedido)(raiz, { ...pedido, estado: 'escalado', detalhe: escolha.razao });
579
+ return {
580
+ pedido,
581
+ playbook: 'escalada',
582
+ slug: pedido.slug,
583
+ despachada: false,
584
+ sessionId: null,
585
+ verificada: false,
586
+ motivo: 'human.pending',
587
+ detalhe: escolha.razao,
588
+ dryRun: opcoes.dryRun === true,
589
+ };
590
+ }
591
+ // Playbooks 1 e 2: mesma sessao (mesmo slug) ou nova sessao (slug rotacionado).
592
+ //
593
+ // A rotacao conta o slug da sessao MORTA junto dos slugs ja usados na thread. A fase
594
+ // morreu antes de virar sessao registrada, entao sem isso o `slugDaSessao` devolveria
595
+ // o mesmo slug e a "nova sessao" nasceria com o nome da que acabou de cair.
596
+ const slug = escolha.playbook === 'nova-sessao'
597
+ ? (0, slug_1.proximaRotacao)(pedido.slug, [...thread.sessoes.map((s) => s.slug), pedido.slug])
598
+ : pedido.slug;
599
+ const r = redespachar(carregado, thread, pedido.fase, pedido.promptPath, pedido.promptSha256, {
600
+ slug,
601
+ model: pedido.model,
602
+ effort: pedido.effort,
603
+ cwd: pedido.cwd,
604
+ origem: 'retry.rate-limit',
605
+ dryRun: opcoes.dryRun,
606
+ });
607
+ (0, ledger_1.registrar)(dir, thread.id, ledger_1.TIPOS_DE_EVENTO.retryTentado, {
608
+ fase: pedido.fase,
609
+ motivo: 'runtime.rate-limited',
610
+ acao: 'esperar-janela',
611
+ playbook: escolha.playbook,
612
+ pedido: pedido.id,
613
+ slug,
614
+ tentativa: pedido.tentativas + 1,
615
+ limite: carregado.manifesto.retry.max_tentativas,
616
+ modo: thread.modo,
617
+ dryRun: opcoes.dryRun === true,
618
+ ok: r.ok,
619
+ detalhe: r.detalhe,
620
+ });
621
+ if (opcoes.dryRun) {
622
+ return {
623
+ pedido,
624
+ playbook: escolha.playbook,
625
+ slug,
626
+ despachada: false,
627
+ sessionId: null,
628
+ verificada: false,
629
+ motivo: r.motivo,
630
+ detalhe: `${escolha.razao}; ${r.detalhe}`,
631
+ dryRun: true,
632
+ };
633
+ }
634
+ if (r.ok) {
635
+ (0, ledger_1.registrar)(dir, thread.id, ledger_1.TIPOS_DE_EVENTO.rateLimitRetomado, {
636
+ fase: pedido.fase,
637
+ pedido: pedido.id,
638
+ playbook: escolha.playbook,
639
+ slug,
640
+ sessionId: r.sessionId,
641
+ promptSha256: pedido.promptSha256,
642
+ esperouAte: pedido.liberaEm,
643
+ janelaEstimada: pedido.janelaEstimada,
644
+ modo: thread.modo,
645
+ autorizadoPor: 'politica de retry do bloco B3 (rate limit nao pausa modo nenhum)',
646
+ evidencia: `mesmo prompt sha256 ${pedido.promptSha256}`,
647
+ razao: escolha.razao,
648
+ });
649
+ pedido = (0, ratelimit_1.gravarPedido)(raiz, {
650
+ ...pedido,
651
+ estado: 'retomado',
652
+ tentativas: pedido.tentativas + 1,
653
+ detalhe: r.detalhe,
654
+ });
655
+ return {
656
+ pedido,
657
+ playbook: escolha.playbook,
658
+ slug,
659
+ despachada: true,
660
+ sessionId: r.sessionId,
661
+ verificada: r.verificada,
662
+ motivo: null,
663
+ detalhe: r.detalhe,
664
+ dryRun: false,
665
+ };
666
+ }
667
+ // Falhou de novo. Rate limit outra vez reagenda o MESMO pedido com a hora nova;
668
+ // qualquer outro motivo devolve o pedido para a fila com a tentativa contada.
669
+ const novaJanela = r.sinal?.resetEm ?? pedido.liberaEm;
670
+ pedido = (0, ratelimit_1.gravarPedido)(raiz, {
671
+ ...pedido,
672
+ tentativas: pedido.tentativas + 1,
673
+ liberaEm: novaJanela,
674
+ janelaEstimada: r.sinal ? r.sinal.resetEm === null : pedido.janelaEstimada,
675
+ sinal: r.sinal ?? pedido.sinal,
676
+ detalhe: r.detalhe,
677
+ });
678
+ return {
679
+ pedido,
680
+ playbook: escolha.playbook,
681
+ slug,
682
+ despachada: false,
683
+ sessionId: null,
684
+ verificada: false,
685
+ motivo: r.motivo,
686
+ detalhe: r.detalhe,
687
+ dryRun: false,
688
+ };
689
+ }
690
+ /** Retoma todos os pedidos cuja janela ja liberou. */
691
+ function retomarFila(carregado, opcoes = {}) {
692
+ const quando = opcoes.quando ?? (0, util_1.agora)();
693
+ const alvos = opcoes.forcar ? (0, ratelimit_1.aguardando)(carregado.raiz) : (0, ratelimit_1.vencidos)(carregado.raiz, quando);
694
+ return alvos.map((p) => retomarPedido(carregado, p, { ...opcoes, quando }));
695
+ }
696
+ /** Retoma um pedido especifico pelo id. */
697
+ function retomarPorId(carregado, id, opcoes = {}) {
698
+ return retomarPedido(carregado, (0, ratelimit_1.exigirPedido)(carregado.raiz, id), opcoes);
699
+ }
700
+ /** Cancela um pedido da fila, com motivo obrigatorio. */
701
+ function cancelarPedido(carregado, id, motivo) {
702
+ const pedido = (0, ratelimit_1.exigirPedido)(carregado.raiz, id);
703
+ const cancelado = (0, ratelimit_1.gravarPedido)(carregado.raiz, {
704
+ ...pedido,
705
+ estado: 'cancelado',
706
+ detalhe: motivo,
707
+ });
708
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(carregado.raiz, pedido.thread), pedido.thread, ledger_1.TIPOS_DE_EVENTO.retryEscalado, {
709
+ fase: pedido.fase,
710
+ motivo: 'runtime.rate-limited',
711
+ origem: 'rate-limit.fila',
712
+ pedido: pedido.id,
713
+ estado: 'cancelado',
714
+ razao: motivo,
715
+ });
716
+ return cancelado;
717
+ }
718
+ /** O ultimo despacho que falhou nesta fase, de onde sai o prompt a redespachar. */
719
+ function ultimoDespachoFalho(raiz, threadId, fase) {
720
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId));
721
+ for (let i = eventos.length - 1; i >= 0; i--) {
722
+ const e = eventos[i];
723
+ if (e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou && e.fase === fase)
724
+ return e;
725
+ }
726
+ return null;
727
+ }
728
+ /**
729
+ * Executa a acao de retry do ultimo gate reprovado da thread.
730
+ *
731
+ * O que ele NAO faz: inventar uma acao que a politica nao previu, executar acao nao
732
+ * automatica sem autorizacao humana registrada, e reexecutar violacao de custo.
733
+ */
734
+ function executarRetry(carregado, threadId, opcoes = {}) {
735
+ const { raiz } = carregado;
736
+ const plano = planejarRetry(carregado, threadId, opcoes);
737
+ const thread = (0, thread_1.lerThread)(raiz, threadId);
738
+ const dir = (0, thread_1.dirThread)(raiz, threadId);
739
+ const vazio = {
740
+ plano,
741
+ executada: false,
742
+ rodada: null,
743
+ reverify: null,
744
+ sync: null,
745
+ redespacho: null,
746
+ fila: [],
747
+ detalhe: plano.razao,
748
+ };
749
+ (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.retryPlanejado, {
750
+ fase: plano.fase,
751
+ motivo: plano.motivo,
752
+ acao: plano.acao,
753
+ modo: thread.modo,
754
+ tentativas: plano.tentativas,
755
+ limite: plano.limite,
756
+ automatica: plano.automatica,
757
+ bloqueio: plano.bloqueio,
758
+ effort: plano.effort,
759
+ razao: plano.razao,
760
+ });
761
+ if (!plano.automatica || plano.motivo === null) {
762
+ // Escalacao e custo viram gate humano explicito no ledger, nao silencio.
763
+ if (plano.bloqueio === 'escalacao.limite' || plano.acao === 'sem-retry') {
764
+ (0, gates_1.registrarGateBloqueado)(dir, threadId, {
765
+ gate: 'phase.dispatch',
766
+ motivo: plano.acao === 'sem-retry' ? plano.motivo : 'human.pending',
767
+ modo: thread.modo,
768
+ detalhe: plano.razao,
769
+ correcao: plano.politica?.correcao ?? `ork gate approve ${threadId} retry --por <quem>`,
770
+ fase: plano.fase,
771
+ origem: 'retry.run',
772
+ pausaQualquerModo: true,
773
+ });
774
+ (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.retryEscalado, {
775
+ fase: plano.fase,
776
+ motivo: plano.motivo,
777
+ origem: 'retry.run',
778
+ acao: plano.acao,
779
+ tentativas: plano.tentativas,
780
+ limite: plano.limite,
781
+ modo: thread.modo,
782
+ razao: plano.razao,
783
+ });
784
+ }
785
+ return vazio;
786
+ }
787
+ const acaoBase = plano.politica?.acao ?? plano.acao;
788
+ const registrarTentativa = (ok, detalhe) => {
789
+ (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.retryTentado, {
790
+ fase: plano.fase,
791
+ motivo: plano.motivo,
792
+ acao: plano.acao,
793
+ acaoBase,
794
+ modo: thread.modo,
795
+ tentativa: plano.tentativas + 1,
796
+ limite: plano.limite,
797
+ effort: plano.effort,
798
+ effortAnterior: plano.effortAnterior,
799
+ dryRun: opcoes.dryRun === true,
800
+ autorizadoPor: blocoPausa(thread, plano.fase)
801
+ ? (opcoes.autorizadoPor ?? 'humano')
802
+ : `politica de retry do bloco B3 (bloco sem pausa no modo ${thread.modo})`,
803
+ ok,
804
+ detalhe,
805
+ });
806
+ };
807
+ // `esperar-janela`: quem retoma e a fila, e ela ja esta gravada em disco.
808
+ if (acaoBase === 'esperar-janela') {
809
+ const fila = (0, ratelimit_1.lerFilaDeRetomada)(raiz).filter((p) => p.thread === threadId && p.estado === 'aguardando');
810
+ const detalhe = fila.length > 0
811
+ ? `${fila.length} pedido(s) na fila duravel; o proximo libera em ${fila[0].liberaEm}`
812
+ : 'nenhum pedido aguardando na fila para esta thread';
813
+ registrarTentativa(fila.length > 0, detalhe);
814
+ return { ...vazio, executada: fila.length > 0, fila, detalhe };
815
+ }
816
+ if (acaoBase === 'sincronizar-worktree') {
817
+ const sync = (0, worktree_1.sincronizarWorktree)(carregado, threadId, { dryRun: opcoes.dryRun });
818
+ const detalhe = sync.ok
819
+ ? `worktree sincronizada com a base (${sync.passos.join('; ')})`
820
+ : `sincronizacao nao concluiu: ${sync.detalhe}`;
821
+ registrarTentativa(sync.ok, detalhe);
822
+ return { ...vazio, executada: sync.ok, sync, detalhe };
823
+ }
824
+ if (acaoBase === 'corrigir-dirigido') {
825
+ const rodada = (0, fix_1.abrirRodada)(carregado, threadId);
826
+ const reverify = opcoes.reverify && rodada.correcoes.length > 0
827
+ ? (0, fix_1.reverificar)(carregado, threadId, { rodada: rodada.rodada })
828
+ : null;
829
+ const detalhe = rodada.correcoes.length === 0
830
+ ? 'o CHECK passou no HEAD real: nao ha correcao a dirigir'
831
+ : `${rodada.correcoes.length} correcao(oes) na rodada ${rodada.rodada}` +
832
+ (reverify ? `; CHECK-REVERIFY: ${reverify.veredito}` : '');
833
+ registrarTentativa(rodada.correcoes.length > 0, detalhe);
834
+ return { ...vazio, executada: rodada.correcoes.length > 0, rodada, reverify, detalhe };
835
+ }
836
+ // `reexecutar` e `escalar-esforco`: o MESMO prompt volta ao runtime.
837
+ const falho = ultimoDespachoFalho(raiz, threadId, plano.fase);
838
+ if (!falho || typeof falho.promptPath !== 'string' || typeof falho.promptSha256 !== 'string') {
839
+ const detalhe = `nao ha despacho falho registrado em ${plano.fase} com prompt gravado: ` +
840
+ `rode a fase de novo com \`ork phase run ${threadId} ${plano.fase} --prompt "..."\``;
841
+ registrarTentativa(false, detalhe);
842
+ return { ...vazio, detalhe };
843
+ }
844
+ const redespacho = redespachar(carregado, thread, plano.fase, falho.promptPath, falho.promptSha256, {
845
+ effort: plano.effort,
846
+ model: typeof falho.model === 'string' ? falho.model : null,
847
+ origem: 'retry.run',
848
+ dryRun: opcoes.dryRun,
849
+ });
850
+ registrarTentativa(redespacho.ok, redespacho.detalhe);
851
+ return {
852
+ ...vazio,
853
+ executada: redespacho.ok,
854
+ redespacho,
855
+ detalhe: redespacho.detalhe,
856
+ };
857
+ }
858
+ /** Texto de `ork retry plan`. */
859
+ function textoDoPlano(p) {
860
+ const linhas = [];
861
+ linhas.push(`Plano de retry da thread ${p.thread}`);
862
+ linhas.push(` modo ${p.modo}`);
863
+ linhas.push(` fase ${p.fase ?? '(nenhuma)'}`);
864
+ linhas.push(` motivo tipado ${p.motivo ?? '(nenhuma reprovacao pendente)'}`);
865
+ if (p.detalhe)
866
+ linhas.push(` detalhe ${p.detalhe}`);
867
+ linhas.push(` acao ${p.acao}`);
868
+ linhas.push(` tentativas ${p.tentativas} de ${p.limite} antes da escalacao`);
869
+ linhas.push(` esforco ${p.effort}${p.effort !== p.effortAnterior ? ` (escalado de ${p.effortAnterior})` : ''}`);
870
+ linhas.push(` automatica ${p.automatica ? 'sim' : 'nao'}`);
871
+ if (p.bloqueio)
872
+ linhas.push(` bloqueio ${p.bloqueio}`);
873
+ linhas.push(` razao ${p.razao}`);
874
+ if (p.politica && !p.automatica)
875
+ linhas.push(` correcao ${p.politica.correcao}`);
876
+ return linhas.join('\n');
877
+ }
878
+ /** Texto de `ork retry run`. */
879
+ function textoDoRetry(r) {
880
+ const linhas = [textoDoPlano(r.plano), ''];
881
+ linhas.push(`Execucao: ${r.executada ? 'acao executada' : 'nada executado'}`);
882
+ linhas.push(` ${r.detalhe}`);
883
+ if (r.rodada && r.rodada.correcoes.length > 0) {
884
+ linhas.push('');
885
+ linhas.push(`GO-FIX rodada ${r.rodada.rodada}: ` +
886
+ r.rodada.correcoes.map((c) => `${c.id} (tipo ${c.tipo}, ${c.origem})`).join(', '));
887
+ }
888
+ if (r.reverify) {
889
+ linhas.push(`CHECK-REVERIFY: ${r.reverify.veredito} (cobertura ${r.reverify.cobertura})`);
890
+ }
891
+ if (r.fila.length > 0) {
892
+ linhas.push('');
893
+ linhas.push('Fila de rate limit desta thread:');
894
+ for (const p of r.fila) {
895
+ linhas.push(` ${p.id} ${p.fase} libera em ${p.liberaEm}${p.janelaEstimada ? ' (janela estimada)' : ''}`);
896
+ }
897
+ }
898
+ return linhas.join('\n');
899
+ }
900
+ /** Texto de `ork retry resume`. */
901
+ function textoDaRetomada(rs) {
902
+ if (rs.length === 0) {
903
+ return 'Nenhum pedido da fila de rate limit com a janela liberada agora.';
904
+ }
905
+ const linhas = [];
906
+ for (const r of rs) {
907
+ linhas.push(`${r.pedido.id} thread ${r.pedido.thread} fase ${r.pedido.fase}`);
908
+ linhas.push(` playbook ${r.playbook}`);
909
+ linhas.push(` slug ${r.slug}`);
910
+ linhas.push(` retomada ${r.despachada ? `sim (sessao ${r.sessionId})` : 'nao'}`);
911
+ if (r.motivo)
912
+ linhas.push(` motivo ${r.motivo}`);
913
+ linhas.push(` detalhe ${r.detalhe}`);
914
+ linhas.push(` estado ${r.pedido.estado}`);
915
+ }
916
+ return linhas.join('\n');
917
+ }