@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.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/adapters/README.md +22 -0
- package/adapters/claude-code/.claude-plugin/marketplace.json +15 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +31 -0
- package/adapters/claude-code/README.md +102 -0
- package/adapters/claude-code/agents/ork-check.md +32 -0
- package/adapters/claude-code/agents/ork-go.md +32 -0
- package/adapters/claude-code/agents/ork-goal.md +33 -0
- package/adapters/claude-code/agents/ork-master.md +32 -0
- package/adapters/claude-code/agents/ork-plan.md +32 -0
- package/adapters/claude-code/agents/ork-ship.md +32 -0
- package/adapters/claude-code/commands/check.md +45 -0
- package/adapters/claude-code/commands/go.md +46 -0
- package/adapters/claude-code/commands/goal.md +54 -0
- package/adapters/claude-code/commands/master.md +42 -0
- package/adapters/claude-code/commands/ork.md +37 -0
- package/adapters/claude-code/commands/plan.md +47 -0
- package/adapters/claude-code/commands/ship.md +45 -0
- package/adapters/claude-code/hooks/hooks.json +17 -0
- package/adapters/claude-code/hooks/ork-guard.js +130 -0
- package/adapters/hermes/README.md +45 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +25 -0
- package/adapters/hermes/hermes.plugin.json +21 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +116 -0
- package/adapters/openclaw/README.md +63 -0
- package/adapters/openclaw/bin/ork-abrir-thread.sh +24 -0
- package/adapters/openclaw/openclaw.plugin.json +138 -0
- package/dist/adapters/claude-bg.js +308 -0
- package/dist/auditoria.js +848 -0
- package/dist/auditrun.js +976 -0
- package/dist/board.js +314 -0
- package/dist/canarios.js +271 -0
- package/dist/catalogo.js +124 -0
- package/dist/ciclos.js +156 -0
- package/dist/claims.js +226 -0
- package/dist/divida.js +374 -0
- package/dist/doctor.js +246 -0
- package/dist/evalrunner.js +458 -0
- package/dist/fix.js +450 -0
- package/dist/gates.js +92 -0
- package/dist/handoff.js +521 -0
- package/dist/hosts.js +331 -0
- package/dist/index.js +1910 -0
- package/dist/init.js +231 -0
- package/dist/leases.js +508 -0
- package/dist/ledger.js +138 -0
- package/dist/manifest.js +255 -0
- package/dist/master.js +453 -0
- package/dist/memoria.js +773 -0
- package/dist/modos.js +220 -0
- package/dist/orkmind.js +487 -0
- package/dist/orquestracao.js +559 -0
- package/dist/phase.js +572 -0
- package/dist/policies.js +176 -0
- package/dist/prompts.js +406 -0
- package/dist/ratelimit.js +179 -0
- package/dist/recall.js +252 -0
- package/dist/retry.js +917 -0
- package/dist/sandbox.js +94 -0
- package/dist/sessoes.js +104 -0
- package/dist/ship.js +551 -0
- package/dist/slug.js +100 -0
- package/dist/superficie.js +1347 -0
- package/dist/thread.js +333 -0
- package/dist/tokens.js +228 -0
- package/dist/types.js +20 -0
- package/dist/util.js +156 -0
- package/dist/verify.js +262 -0
- package/dist/worktree.js +576 -0
- package/dist/yaml.js +112 -0
- package/eval/README.md +85 -0
- package/eval/casos/check-quality.json +103 -0
- package/eval/casos/code-reviewer.json +102 -0
- package/eval/casos/decision-triage.json +103 -0
- package/eval/casos/go-implementation.json +103 -0
- package/eval/casos/goal-definition.json +103 -0
- package/eval/casos/master-metrics.json +103 -0
- package/eval/casos/narrative-guardian.json +163 -0
- package/eval/casos/orkastery-bootstrap.json +102 -0
- package/eval/casos/plan-specification.json +102 -0
- package/eval/casos/roadmap-keeper.json +103 -0
- package/eval/casos/scope-check-capability-map.json +83 -0
- package/eval/casos/security-auditor.json +102 -0
- package/eval/casos/ship-release.json +122 -0
- package/eval/casos/test-engineer.json +122 -0
- package/eval/casos/thread-state.json +82 -0
- package/eval/casos/thread-tracing.json +83 -0
- package/eval/casos/web-performance-auditor.json +102 -0
- package/eval/fixtures/b0-slug-e-modos/caso.json +56 -0
- package/eval/fixtures/b2-master-log/caso.json +91 -0
- package/eval/fixtures/fx-concurrency/caso.json +15 -0
- package/eval/fixtures/fx-hallucination/caso.json +12 -0
- package/eval/fixtures/fx-happy/caso.json +16 -0
- package/eval/fixtures/fx-schema-drift/caso.json +15 -0
- package/eval/fixtures/fx-stale-base/caso.json +12 -0
- package/eval/fixtures/fx-wiki-destroy/caso.json +16 -0
- package/eval/fixtures/superficie-de-rede/README.md +22 -0
- package/eval/fixtures/superficie-de-rede/api-express.js +30 -0
- package/eval/fixtures/superficie-de-rede/api_fastapi.py +17 -0
- package/eval/fixtures/superficie-de-rede/api_gin.go +18 -0
- package/package.json +56 -0
- package/references/README.md +25 -0
- package/references/code-review-axes.md +82 -0
- package/references/definition-of-done.md +74 -0
- package/references/performance-checklist.md +53 -0
- package/references/security-checklist.md +101 -0
- package/references/testing-patterns.md +51 -0
- package/skills/README.md +43 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +89 -0
- package/skills/core/thread-state/SKILL.md +86 -0
- package/skills/governance/decision-triage/SKILL.md +65 -0
- package/skills/governance/narrative-guardian/SKILL.md +66 -0
- package/skills/governance/roadmap-keeper/SKILL.md +60 -0
- package/skills/governance/scope-check-capability-map/SKILL.md +61 -0
- package/skills/observability/thread-tracing/SKILL.md +63 -0
- package/skills/phases/check-quality/SKILL.md +87 -0
- package/skills/phases/go-implementation/SKILL.md +70 -0
- package/skills/phases/goal-definition/SKILL.md +69 -0
- package/skills/phases/master-metrics/SKILL.md +65 -0
- package/skills/phases/plan-specification/SKILL.md +67 -0
- package/skills/phases/ship-release/SKILL.md +68 -0
- package/skills/reviewers/code-reviewer/SKILL.md +61 -0
- package/skills/reviewers/security-auditor/SKILL.md +68 -0
- package/skills/reviewers/test-engineer/SKILL.md +66 -0
- package/skills/reviewers/web-performance-auditor/SKILL.md +63 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `ork orquestracao status` (alias `ork monitor`): quem esta parado, esperando o que.
|
|
4
|
+
*
|
|
5
|
+
* O problema que este arquivo resolve e de CONDUCAO, nao de execucao: uma thread que
|
|
6
|
+
* fecha um bloco com pausa humana fica esperando veredito e ninguem e avisado; uma
|
|
7
|
+
* thread que perde a corrida do lease fica na fila e ninguem e avisado. O orquestrador
|
|
8
|
+
* so descobre quando o humano pergunta, e ate la o roadmap ja atrasou.
|
|
9
|
+
*
|
|
10
|
+
* O comando e a resposta deterministica a essa pergunta, de UMA chamada:
|
|
11
|
+
* - quais threads estao paradas em PAUSA HUMANA (HITL), esperando veredito;
|
|
12
|
+
* - quais estao em IMPEDIMENTO (lease em colisao, gate tipado reprovado, limite de uso
|
|
13
|
+
* do runtime, limite de paralelismo da maquina);
|
|
14
|
+
* - o que cada pausa exige do humano (`pausaSobre` do bloco), com evidencia e correcao;
|
|
15
|
+
* - ha quanto tempo cada uma esta parada.
|
|
16
|
+
*
|
|
17
|
+
* Tres regras que mandam aqui:
|
|
18
|
+
*
|
|
19
|
+
* 1. **Nada de estado novo.** Toda parada e DERIVADA do que ja existe em disco:
|
|
20
|
+
* `thread.json`, `ledger.jsonl`, `.orkastery/leases/fila.json` e
|
|
21
|
+
* `.orkastery/retry/fila.jsonl`. O monitor le, nunca grava. Por isso ele e seguro
|
|
22
|
+
* de rodar em laco, e por isso ele funciona em thread criada antes deste comando.
|
|
23
|
+
* 2. **O `ork` entrega o estado, nao manda mensagem.** Notificar o humano e do
|
|
24
|
+
* orquestrador (Camada 1), que consome o `--json`. O nucleo continua sem LLM.
|
|
25
|
+
* 3. **Fonte declarada.** Quando o runtime nao pode ser consultado para saber se a
|
|
26
|
+
* sessao do bloco ainda esta viva, o monitor diz isso (`sessaoViva: null`) e avisa
|
|
27
|
+
* A MAIS. Perder uma pausa custa roadmap; avisar de uma pausa que ainda nao chegou
|
|
28
|
+
* custa uma linha de tabela.
|
|
29
|
+
*/
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.ATENCAO_PADRAO_MIN = void 0;
|
|
65
|
+
exports.minutosEntre = minutosEntre;
|
|
66
|
+
exports.duracaoCurta = duracaoCurta;
|
|
67
|
+
exports.montarMonitor = montarMonitor;
|
|
68
|
+
exports.textoDoMonitor = textoDoMonitor;
|
|
69
|
+
const adapter = __importStar(require("./adapters/claude-bg"));
|
|
70
|
+
const board_1 = require("./board");
|
|
71
|
+
const gates_1 = require("./gates");
|
|
72
|
+
const ledger_1 = require("./ledger");
|
|
73
|
+
const modos_1 = require("./modos");
|
|
74
|
+
const ratelimit_1 = require("./ratelimit");
|
|
75
|
+
const thread_1 = require("./thread");
|
|
76
|
+
const util_1 = require("./util");
|
|
77
|
+
/** Limite de atencao padrao, em minutos: parada mais velha que isso e destacada. */
|
|
78
|
+
exports.ATENCAO_PADRAO_MIN = 30;
|
|
79
|
+
/** Estado que o `ork phase run` grava quando o bloco despachado pausa ao fim. */
|
|
80
|
+
const PAUSA_PREVISTA = 'prevista ao fim do bloco';
|
|
81
|
+
/**
|
|
82
|
+
* Eventos que destravam uma reprovacao de gate.
|
|
83
|
+
*
|
|
84
|
+
* `phase_dispatch` entra pela porta da frente: o `ork phase run` grava `gate_blocked`
|
|
85
|
+
* ANTES de despachar e aborta o despacho, entao um `phase_dispatch` posterior so existe
|
|
86
|
+
* se aquele bloqueio deixou de valer.
|
|
87
|
+
*/
|
|
88
|
+
const EVENTOS_QUE_DESTRAVAM = [
|
|
89
|
+
ledger_1.TIPOS_DE_EVENTO.gateLiberado,
|
|
90
|
+
ledger_1.TIPOS_DE_EVENTO.faseDespachada,
|
|
91
|
+
ledger_1.TIPOS_DE_EVENTO.leaseAdquirido,
|
|
92
|
+
ledger_1.TIPOS_DE_EVENTO.rateLimitRetomado,
|
|
93
|
+
ledger_1.TIPOS_DE_EVENTO.shipConcluido,
|
|
94
|
+
ledger_1.TIPOS_DE_EVENTO.masterConcluido,
|
|
95
|
+
];
|
|
96
|
+
/** O evento e uma autorizacao humana ja registrada (`ork gate approve`)? */
|
|
97
|
+
function ehAprovacaoHumana(e) {
|
|
98
|
+
return e.tipo === ledger_1.TIPOS_DE_EVENTO.pausaHumana && e.estado === 'aprovado';
|
|
99
|
+
}
|
|
100
|
+
/** O evento e a pausa PREVISTA gravada no despacho do ultimo bloco? */
|
|
101
|
+
function ehPausaPrevista(e) {
|
|
102
|
+
return e.tipo === ledger_1.TIPOS_DE_EVENTO.pausaHumana && e.estado === PAUSA_PREVISTA;
|
|
103
|
+
}
|
|
104
|
+
/** Minutos inteiros entre dois carimbos ISO (nunca negativo). */
|
|
105
|
+
function minutosEntre(desdeEm, ateEm) {
|
|
106
|
+
const inicio = Date.parse(desdeEm);
|
|
107
|
+
const fim = Date.parse(ateEm);
|
|
108
|
+
if (!Number.isFinite(inicio) || !Number.isFinite(fim))
|
|
109
|
+
return 0;
|
|
110
|
+
return Math.max(0, Math.floor((fim - inicio) / 60000));
|
|
111
|
+
}
|
|
112
|
+
/** Duracao curta para a tabela: `12min`, `3h05`, `2d 04h`. */
|
|
113
|
+
function duracaoCurta(minutos) {
|
|
114
|
+
if (minutos < 60)
|
|
115
|
+
return `${minutos}min`;
|
|
116
|
+
const horas = Math.floor(minutos / 60);
|
|
117
|
+
if (horas < 24)
|
|
118
|
+
return `${horas}h${String(minutos % 60).padStart(2, '0')}`;
|
|
119
|
+
return `${Math.floor(horas / 24)}d ${String(horas % 24).padStart(2, '0')}h`;
|
|
120
|
+
}
|
|
121
|
+
/** Indice da fase no ciclo REAL da thread (a variante de ciclo pode encurtar o ciclo). */
|
|
122
|
+
function indiceDaFase(thread, fase) {
|
|
123
|
+
return typeof fase === 'string' ? thread.fases.indexOf(fase) : -1;
|
|
124
|
+
}
|
|
125
|
+
/** Fase valida do ciclo da thread, ou null quando o evento nao carrega fase util. */
|
|
126
|
+
function faseDoEvento(thread, e) {
|
|
127
|
+
const i = indiceDaFase(thread, e.fase);
|
|
128
|
+
return i >= 0 ? thread.fases[i] : null;
|
|
129
|
+
}
|
|
130
|
+
/** Texto do bloco de uma fase (`GOAL-PLAN`), vazio quando a fase nao pertence a bloco. */
|
|
131
|
+
function blocoDaFase(thread, fase) {
|
|
132
|
+
if (!fase)
|
|
133
|
+
return { bloco: '', pausaSobre: '' };
|
|
134
|
+
try {
|
|
135
|
+
const b = (0, thread_1.blocoDaThread)(thread, fase);
|
|
136
|
+
return { bloco: b.fases.join('-'), pausaSobre: b.pausa ? b.pausaSobre : '' };
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return { bloco: '', pausaSobre: '' };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Comando que o humano roda para liberar uma pausa da thread. */
|
|
143
|
+
function comandoDeAprovacao(threadId, sobre) {
|
|
144
|
+
return `ork gate approve ${threadId} "${sobre || 'pausa'}" --por <quem>`;
|
|
145
|
+
}
|
|
146
|
+
function parada(dados, quando) {
|
|
147
|
+
return { ...dados, paradaHaMin: minutosEntre(dados.desdeEm, quando) };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* As pausas humanas ABERTAS da thread.
|
|
151
|
+
*
|
|
152
|
+
* Tres fontes, na ordem de confianca. A mesma fase nunca vira duas linhas: a primeira
|
|
153
|
+
* fonte que a reivindica fica com ela, e as demais so acrescentam evidencia.
|
|
154
|
+
*/
|
|
155
|
+
function pausasAbertas(thread, eventos, vivas, quando) {
|
|
156
|
+
const porFase = new Map();
|
|
157
|
+
if (thread.status === 'fechada')
|
|
158
|
+
return [];
|
|
159
|
+
// Fonte 1: o ledger, que e onde a pausa prevista pelo modo fica registrada com a
|
|
160
|
+
// sessao que a produziu. Aberta enquanto ninguem aprovou e a conducao nao passou
|
|
161
|
+
// para uma fase POSTERIOR (redespachar a mesma fase nao resolve pausa nenhuma).
|
|
162
|
+
eventos.forEach((e, i) => {
|
|
163
|
+
if (!ehPausaPrevista(e))
|
|
164
|
+
return;
|
|
165
|
+
const fase = faseDoEvento(thread, e);
|
|
166
|
+
const iFase = fase ? thread.fases.indexOf(fase) : -1;
|
|
167
|
+
const resolvida = eventos.slice(i + 1).some((p) => {
|
|
168
|
+
if (ehAprovacaoHumana(p))
|
|
169
|
+
return true;
|
|
170
|
+
if (p.tipo === ledger_1.TIPOS_DE_EVENTO.masterConcluido)
|
|
171
|
+
return true;
|
|
172
|
+
return p.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada && indiceDaFase(thread, p.fase) > iFase;
|
|
173
|
+
});
|
|
174
|
+
if (resolvida)
|
|
175
|
+
return;
|
|
176
|
+
const sessionId = typeof e.sessionId === 'string' ? e.sessionId : '';
|
|
177
|
+
const sessaoViva = vivas === null ? null : vivas.has(sessionId);
|
|
178
|
+
const sobre = typeof e.previstaSobre === 'string' && e.previstaSobre !== ''
|
|
179
|
+
? e.previstaSobre
|
|
180
|
+
: blocoDaFase(thread, fase).pausaSobre;
|
|
181
|
+
const chave = fase ?? `evento-${i}`;
|
|
182
|
+
porFase.set(chave, parada({
|
|
183
|
+
natureza: 'pausa-humana',
|
|
184
|
+
motivo: 'human.pending',
|
|
185
|
+
fase,
|
|
186
|
+
bloco: blocoDaFase(thread, fase).bloco,
|
|
187
|
+
pausaSobre: sobre,
|
|
188
|
+
detalhe: sessaoViva === true
|
|
189
|
+
? `a sessao ${sessionId.slice(0, 8)} do bloco ainda esta viva no runtime: a pausa chega quando ela terminar`
|
|
190
|
+
: `bloco fechado em ${fase ?? '?'}: espera o veredito humano sobre ${sobre || 'a pausa do bloco'}`,
|
|
191
|
+
evidencia: `ledger ${ledger_1.TIPOS_DE_EVENTO.pausaHumana} (${PAUSA_PREVISTA})${sessionId ? `, sessao ${sessionId.slice(0, 8)}` : ''}`,
|
|
192
|
+
correcao: comandoDeAprovacao(thread.id, sobre),
|
|
193
|
+
desdeEm: e.ts,
|
|
194
|
+
sessaoViva,
|
|
195
|
+
fonte: 'ledger',
|
|
196
|
+
}, quando));
|
|
197
|
+
});
|
|
198
|
+
// Fonte 2: `gate_blocked` com motivo `human.pending`. E por aqui que aparece a
|
|
199
|
+
// ESCALADA do bloco B3 (limite de tentativas estourado), que pausa qualquer modo,
|
|
200
|
+
// inclusive `#Auto` -- que por definicao nao tem pausa prevista no ledger.
|
|
201
|
+
eventos.forEach((e, i) => {
|
|
202
|
+
if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.gateBloqueado || e.motivo !== 'human.pending')
|
|
203
|
+
return;
|
|
204
|
+
const resolvido = eventos
|
|
205
|
+
.slice(i + 1)
|
|
206
|
+
.some((p) => EVENTOS_QUE_DESTRAVAM.includes(p.tipo) || ehAprovacaoHumana(p));
|
|
207
|
+
if (resolvido)
|
|
208
|
+
return;
|
|
209
|
+
const fase = faseDoEvento(thread, e);
|
|
210
|
+
const chave = fase ?? `gate-${i}`;
|
|
211
|
+
if (porFase.has(chave))
|
|
212
|
+
return;
|
|
213
|
+
const { bloco, pausaSobre } = blocoDaFase(thread, fase);
|
|
214
|
+
porFase.set(chave, parada({
|
|
215
|
+
natureza: 'pausa-humana',
|
|
216
|
+
motivo: 'human.pending',
|
|
217
|
+
fase,
|
|
218
|
+
bloco,
|
|
219
|
+
pausaSobre: pausaSobre || 'autorizacao da retomada',
|
|
220
|
+
detalhe: typeof e.detalhe === 'string' && e.detalhe !== ''
|
|
221
|
+
? e.detalhe
|
|
222
|
+
: gates_1.DESCRICAO_DO_MOTIVO['human.pending'],
|
|
223
|
+
evidencia: `ledger ${ledger_1.TIPOS_DE_EVENTO.gateBloqueado} (gate ${String(e.gate ?? '?')})`,
|
|
224
|
+
correcao: typeof e.correcao === 'string' && e.correcao !== ''
|
|
225
|
+
? e.correcao
|
|
226
|
+
: comandoDeAprovacao(thread.id, pausaSobre),
|
|
227
|
+
desdeEm: e.ts,
|
|
228
|
+
sessaoViva: null,
|
|
229
|
+
fonte: 'ledger',
|
|
230
|
+
}, quando));
|
|
231
|
+
});
|
|
232
|
+
// Fonte 3: `status: pausada` carimbado no proprio `thread.json`. E a fonte mais fraca
|
|
233
|
+
// (nao diz sobre o que) e a mais duravel (sobrevive a ledger truncado), entao ela so
|
|
234
|
+
// entra quando a fase atual ainda nao virou linha.
|
|
235
|
+
if (thread.status === 'pausada' && !porFase.has(thread.faseAtual)) {
|
|
236
|
+
const { bloco, pausaSobre } = blocoDaFase(thread, thread.faseAtual);
|
|
237
|
+
porFase.set(thread.faseAtual, parada({
|
|
238
|
+
natureza: 'pausa-humana',
|
|
239
|
+
motivo: 'human.pending',
|
|
240
|
+
fase: thread.faseAtual,
|
|
241
|
+
bloco,
|
|
242
|
+
pausaSobre: pausaSobre || 'veredito do bloco',
|
|
243
|
+
detalhe: `thread carimbada como pausada na fase ${thread.faseAtual}`,
|
|
244
|
+
evidencia: 'thread.json (status: pausada)',
|
|
245
|
+
correcao: comandoDeAprovacao(thread.id, pausaSobre),
|
|
246
|
+
desdeEm: thread.atualizadaEm,
|
|
247
|
+
sessaoViva: null,
|
|
248
|
+
fonte: 'thread.json',
|
|
249
|
+
}, quando));
|
|
250
|
+
}
|
|
251
|
+
return [...porFase.values()];
|
|
252
|
+
}
|
|
253
|
+
/** Os impedimentos ABERTOS da thread: o que trava sem ser veredito humano. */
|
|
254
|
+
function impedimentosAbertos(item, eventos, raizDoProjeto, quando) {
|
|
255
|
+
const { thread } = item;
|
|
256
|
+
if (thread.status === 'fechada')
|
|
257
|
+
return [];
|
|
258
|
+
const saida = [];
|
|
259
|
+
// 1. Fila por colisao de regiao: o lease nao foi adquirido porque outra thread esta
|
|
260
|
+
// na mesma regiao. A fila e FIFO e diz quem bloqueia quem.
|
|
261
|
+
for (const p of item.naFila) {
|
|
262
|
+
saida.push(parada({
|
|
263
|
+
natureza: 'impedimento',
|
|
264
|
+
motivo: 'lease.busy',
|
|
265
|
+
fase: thread.faseAtual,
|
|
266
|
+
bloco: blocoDaFase(thread, thread.faseAtual).bloco,
|
|
267
|
+
pausaSobre: '',
|
|
268
|
+
detalhe: `na fila de ${p.nome}, atras da thread ${p.bloqueadaPor} (colide com ${p.colidiuCom})`,
|
|
269
|
+
evidencia: `.orkastery/leases/fila.json (${p.tipo})`,
|
|
270
|
+
correcao: `espere a thread ${p.bloqueadaPor} liberar (ork lease release ${p.colidiuCom} --thread ${p.bloqueadaPor}), ou reduza a regiao pedida`,
|
|
271
|
+
desdeEm: p.desdeEm,
|
|
272
|
+
sessaoViva: null,
|
|
273
|
+
fonte: 'fila-de-lease',
|
|
274
|
+
}, quando));
|
|
275
|
+
}
|
|
276
|
+
// 2. Fila DURAVEL de rate limit: a fase morreu no limite de uso da assinatura e espera
|
|
277
|
+
// a janela seguinte. O impedimento e real, mas tem hora para acabar.
|
|
278
|
+
for (const p of (0, ratelimit_1.lerFilaDeRetomada)(raizDoProjeto)) {
|
|
279
|
+
if (p.thread !== thread.id || p.estado !== 'aguardando')
|
|
280
|
+
continue;
|
|
281
|
+
saida.push(parada({
|
|
282
|
+
natureza: 'impedimento',
|
|
283
|
+
motivo: 'runtime.rate-limited',
|
|
284
|
+
fase: p.fase,
|
|
285
|
+
bloco: blocoDaFase(thread, p.fase).bloco,
|
|
286
|
+
pausaSobre: '',
|
|
287
|
+
detalhe: `pedido ${p.id} na fila de rate limit, libera ${(0, ratelimit_1.faltaPara)(p, quando)} ` +
|
|
288
|
+
`(${p.liberaEm.replace('T', ' ').slice(0, 16)}, ${p.janelaEstimada ? 'janela estimada' : 'hora dita pelo runtime'})`,
|
|
289
|
+
evidencia: `.orkastery/retry/fila.jsonl (${p.id}, ${p.sinal.fonte})`,
|
|
290
|
+
correcao: `ork retry resume --id ${p.id}`,
|
|
291
|
+
desdeEm: p.criadoEm,
|
|
292
|
+
sessaoViva: null,
|
|
293
|
+
fonte: 'fila-de-rate-limit',
|
|
294
|
+
}, quando));
|
|
295
|
+
}
|
|
296
|
+
// 3. Gates tipados reprovados que ninguem destravou. `human.pending` sai daqui porque
|
|
297
|
+
// ele ja e pausa humana, e `claims.unverifiable` avisa mas nao bloqueia.
|
|
298
|
+
eventos.forEach((e, i) => {
|
|
299
|
+
if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.gateBloqueado)
|
|
300
|
+
return;
|
|
301
|
+
const motivo = e.motivo;
|
|
302
|
+
if (motivo === 'human.pending' || motivo === 'claims.unverifiable')
|
|
303
|
+
return;
|
|
304
|
+
if (!(motivo in gates_1.DESCRICAO_DO_MOTIVO))
|
|
305
|
+
return;
|
|
306
|
+
// Lease e rate limit ja tem fila propria em disco, que e a fonte mais fresca.
|
|
307
|
+
if (motivo === 'lease.busy' || motivo === 'runtime.rate-limited')
|
|
308
|
+
return;
|
|
309
|
+
const resolvido = eventos
|
|
310
|
+
.slice(i + 1)
|
|
311
|
+
.some((p) => EVENTOS_QUE_DESTRAVAM.includes(p.tipo) || ehAprovacaoHumana(p));
|
|
312
|
+
if (resolvido)
|
|
313
|
+
return;
|
|
314
|
+
const fase = faseDoEvento(thread, e);
|
|
315
|
+
saida.push(parada({
|
|
316
|
+
natureza: 'impedimento',
|
|
317
|
+
motivo,
|
|
318
|
+
fase,
|
|
319
|
+
bloco: blocoDaFase(thread, fase).bloco,
|
|
320
|
+
pausaSobre: '',
|
|
321
|
+
detalhe: typeof e.detalhe === 'string' && e.detalhe !== ''
|
|
322
|
+
? e.detalhe
|
|
323
|
+
: gates_1.DESCRICAO_DO_MOTIVO[motivo],
|
|
324
|
+
evidencia: `ledger ${ledger_1.TIPOS_DE_EVENTO.gateBloqueado} (gate ${String(e.gate ?? '?')})`,
|
|
325
|
+
correcao: typeof e.correcao === 'string' && e.correcao !== ''
|
|
326
|
+
? e.correcao
|
|
327
|
+
: `ork retry plan ${thread.id} --motivo ${motivo}`,
|
|
328
|
+
desdeEm: e.ts,
|
|
329
|
+
sessaoViva: null,
|
|
330
|
+
fonte: 'ledger',
|
|
331
|
+
}, quando));
|
|
332
|
+
});
|
|
333
|
+
return saida;
|
|
334
|
+
}
|
|
335
|
+
/** Situacao da thread quando o escalonador do perfil principal nao a cobre. */
|
|
336
|
+
function situacaoDerivada(item, temPausa) {
|
|
337
|
+
if (item.thread.status === 'fechada')
|
|
338
|
+
return 'fechada';
|
|
339
|
+
if (item.thread.status === 'pausada' || temPausa)
|
|
340
|
+
return 'pausada';
|
|
341
|
+
if (item.leases.length > 0)
|
|
342
|
+
return 'em-andamento';
|
|
343
|
+
if (item.naFila.length > 0)
|
|
344
|
+
return 'espera';
|
|
345
|
+
return 'pode-avancar';
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Situacao final da thread na visao do monitor.
|
|
349
|
+
*
|
|
350
|
+
* O escalonador (`ork board plan`) so olha `thread.status` e os leases, entao ele nao ve
|
|
351
|
+
* a pausa que ainda nao foi carimbada no `thread.json` nem o gate reprovado no ledger.
|
|
352
|
+
* O monitor sabe mais, e corrige a situacao para o que esta acontecendo de fato.
|
|
353
|
+
*/
|
|
354
|
+
function situacaoDaLinha(item, vaga, precisaDeHumano, temImpedimento) {
|
|
355
|
+
if (item.thread.status === 'fechada')
|
|
356
|
+
return 'fechada';
|
|
357
|
+
if (precisaDeHumano)
|
|
358
|
+
return 'pausada';
|
|
359
|
+
const base = vaga ? vaga.situacao : situacaoDerivada(item, false);
|
|
360
|
+
// Rate limit e gate tipado reprovado nao passam pelo escalonador, mas seguram a thread
|
|
361
|
+
// do mesmo jeito: `pode-avancar` com impedimento aberto seria mentira.
|
|
362
|
+
return temImpedimento && base === 'pode-avancar' ? 'espera' : base;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Monta a visao agregada de pausas e impedimentos do projeto.
|
|
366
|
+
*
|
|
367
|
+
* Custo de leitura: um `thread.json` e um `ledger.jsonl` por thread, mais as duas filas
|
|
368
|
+
* do projeto, mais no maximo uma chamada ao runtime para a lista de sessoes vivas.
|
|
369
|
+
*/
|
|
370
|
+
function montarMonitor(carregado, opcoes = {}) {
|
|
371
|
+
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
372
|
+
const atencaoMin = opcoes.atencaoMin ?? exports.ATENCAO_PADRAO_MIN;
|
|
373
|
+
const todos = opcoes.todos === true;
|
|
374
|
+
const itens = (0, board_1.threadsDeTodosOsPerfis)(carregado, todos);
|
|
375
|
+
// O runtime e a unica fonte que sabe se a sessao do bloco ainda trabalha. Sem ele, a
|
|
376
|
+
// pausa prevista e reportada como valendo: avisar a mais e a postura correta aqui.
|
|
377
|
+
let vivas = null;
|
|
378
|
+
let runtimeDetalhe = 'runtime nao consultado por --sem-runtime';
|
|
379
|
+
if (opcoes.semRuntime !== true) {
|
|
380
|
+
if (adapter.disponivel()) {
|
|
381
|
+
vivas = new Set(adapter.listarSessoes().map((s) => s.sessionId).filter((s) => !!s));
|
|
382
|
+
runtimeDetalhe = '';
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
runtimeDetalhe = 'runtime adapter indisponivel nesta maquina (`claude` fora do PATH)';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// O escalonador so conhece o perfil do manifesto; os demais caem na situacao derivada.
|
|
389
|
+
const vagas = new Map((0, board_1.planejar)(carregado).vagas.map((v) => [v.thread, v]));
|
|
390
|
+
const linhas = [];
|
|
391
|
+
for (const item of itens) {
|
|
392
|
+
const { thread } = item;
|
|
393
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(item.raiz, thread.id));
|
|
394
|
+
const pausas = pausasAbertas(thread, eventos, vivas, quando);
|
|
395
|
+
const impedimentos = impedimentosAbertos(item, eventos, item.raiz, quando);
|
|
396
|
+
// O limite de paralelismo da maquina tambem trava, e o escalonador ja o calculou.
|
|
397
|
+
const vaga = item.raiz === carregado.raiz ? vagas.get(thread.id) : undefined;
|
|
398
|
+
if (vaga && vaga.motivo === 'concurrency.limite') {
|
|
399
|
+
impedimentos.push(parada({
|
|
400
|
+
natureza: 'impedimento',
|
|
401
|
+
motivo: 'concurrency.limite',
|
|
402
|
+
fase: thread.faseAtual,
|
|
403
|
+
bloco: blocoDaFase(thread, thread.faseAtual).bloco,
|
|
404
|
+
pausaSobre: '',
|
|
405
|
+
detalhe: vaga.detalhe,
|
|
406
|
+
evidencia: 'ork board plan (concurrency.max_parallel_threads)',
|
|
407
|
+
correcao: vaga.correcao,
|
|
408
|
+
desdeEm: thread.atualizadaEm,
|
|
409
|
+
sessaoViva: null,
|
|
410
|
+
fonte: 'escalonador',
|
|
411
|
+
}, quando));
|
|
412
|
+
}
|
|
413
|
+
const paradas = [...pausas, ...impedimentos].sort((a, b) => a.desdeEm.localeCompare(b.desdeEm));
|
|
414
|
+
const ultimo = eventos.length > 0 ? eventos[eventos.length - 1] : null;
|
|
415
|
+
const precisaDeHumano = pausas.some((p) => p.sessaoViva !== true);
|
|
416
|
+
const paradaHaMin = paradas.length > 0 ? Math.max(...paradas.map((p) => p.paradaHaMin)) : null;
|
|
417
|
+
linhas.push({
|
|
418
|
+
perfil: item.perfil,
|
|
419
|
+
thread: thread.id,
|
|
420
|
+
slug: thread.slug,
|
|
421
|
+
nome: thread.nome,
|
|
422
|
+
modo: thread.modo,
|
|
423
|
+
tag: (0, modos_1.tagDoModo)(thread.modo),
|
|
424
|
+
faseAtual: thread.faseAtual,
|
|
425
|
+
status: thread.status,
|
|
426
|
+
situacao: situacaoDaLinha(item, vaga, precisaDeHumano, impedimentos.length > 0),
|
|
427
|
+
pausas,
|
|
428
|
+
impedimentos,
|
|
429
|
+
paradas,
|
|
430
|
+
ultimoEvento: ultimo
|
|
431
|
+
? { tipo: ultimo.tipo, ts: ultimo.ts, fase: typeof ultimo.fase === 'string' ? ultimo.fase : null }
|
|
432
|
+
: null,
|
|
433
|
+
paradaHaMin,
|
|
434
|
+
precisaDeHumano,
|
|
435
|
+
temImpedimento: impedimentos.length > 0,
|
|
436
|
+
acimaDoLimite: paradaHaMin !== null && paradaHaMin >= atencaoMin,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
// Ordem da tabela: quem espera humano primeiro, depois quem tem impedimento, e dentro
|
|
440
|
+
// de cada grupo a parada mais VELHA no topo. E a ordem em que o humano deve atacar.
|
|
441
|
+
linhas.sort((a, b) => {
|
|
442
|
+
const peso = (l) => (l.precisaDeHumano ? 0 : l.temImpedimento ? 1 : 2);
|
|
443
|
+
if (peso(a) !== peso(b))
|
|
444
|
+
return peso(a) - peso(b);
|
|
445
|
+
if ((b.paradaHaMin ?? -1) !== (a.paradaHaMin ?? -1)) {
|
|
446
|
+
return (b.paradaHaMin ?? -1) - (a.paradaHaMin ?? -1);
|
|
447
|
+
}
|
|
448
|
+
return a.thread.localeCompare(b.thread);
|
|
449
|
+
});
|
|
450
|
+
const visiveis = opcoes.soParadas === true
|
|
451
|
+
? linhas.filter((l) => l.precisaDeHumano || l.temImpedimento)
|
|
452
|
+
: linhas;
|
|
453
|
+
const perfis = (todos ? (0, board_1.perfisDetectados)(carregado) : (0, board_1.perfisDetectados)(carregado).slice(0, 1)).map((p) => p.nome);
|
|
454
|
+
return {
|
|
455
|
+
projeto: carregado.manifesto.project.name,
|
|
456
|
+
consultadoEm: quando,
|
|
457
|
+
atencaoMin,
|
|
458
|
+
runtimeConsultado: vivas !== null,
|
|
459
|
+
runtimeDetalhe,
|
|
460
|
+
perfis,
|
|
461
|
+
linhas: visiveis,
|
|
462
|
+
resumo: {
|
|
463
|
+
threads: linhas.length,
|
|
464
|
+
aguardandoHumano: linhas.filter((l) => l.precisaDeHumano).length,
|
|
465
|
+
comImpedimento: linhas.filter((l) => l.temImpedimento).length,
|
|
466
|
+
emAndamento: linhas.filter((l) => l.situacao === 'em-andamento').length,
|
|
467
|
+
podemAvancar: linhas.filter((l) => l.situacao === 'pode-avancar').length,
|
|
468
|
+
fechadas: linhas.filter((l) => l.situacao === 'fechada').length,
|
|
469
|
+
acimaDoLimite: linhas.filter((l) => l.acimaDoLimite).length,
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/** Uma parada em uma linha, para os blocos de detalhe abaixo da tabela. */
|
|
474
|
+
function linhaDaParada(p) {
|
|
475
|
+
const L = [];
|
|
476
|
+
L.push(` [${p.motivo}] parada ha ${duracaoCurta(p.paradaHaMin)} (desde ${p.desdeEm.replace('T', ' ').slice(0, 16)})`);
|
|
477
|
+
if (p.pausaSobre)
|
|
478
|
+
L.push(` o humano decide: ${p.pausaSobre}`);
|
|
479
|
+
L.push(` detalhe : ${p.detalhe}`);
|
|
480
|
+
L.push(` evidencia: ${p.evidencia}`);
|
|
481
|
+
L.push(` correcao : ${p.correcao}`);
|
|
482
|
+
return L;
|
|
483
|
+
}
|
|
484
|
+
/** Texto de `ork orquestracao status`. */
|
|
485
|
+
function textoDoMonitor(m) {
|
|
486
|
+
const L = [];
|
|
487
|
+
L.push(`Monitor de orquestracao, projeto ${m.projeto} ` +
|
|
488
|
+
`(${m.resumo.threads} thread(s) em ${m.perfis.length} perfil(is): ${m.perfis.join(', ')})`);
|
|
489
|
+
L.push(` aguardando humano: ${m.resumo.aguardandoHumano} | com impedimento: ${m.resumo.comImpedimento} | ` +
|
|
490
|
+
`em andamento: ${m.resumo.emAndamento} | podem avancar: ${m.resumo.podemAvancar} | ` +
|
|
491
|
+
`fechadas: ${m.resumo.fechadas}`);
|
|
492
|
+
L.push(` limite de atencao: ${m.atencaoMin} min (${m.resumo.acimaDoLimite} parada(s) acima, marcadas com !)`);
|
|
493
|
+
if (!m.runtimeConsultado) {
|
|
494
|
+
L.push(` [warn] ${m.runtimeDetalhe}: pausa prevista e reportada como valendo`);
|
|
495
|
+
}
|
|
496
|
+
L.push('');
|
|
497
|
+
if (m.linhas.length === 0) {
|
|
498
|
+
L.push(' Nenhuma thread parada: ninguem esperando veredito humano nem impedido.');
|
|
499
|
+
return L.join('\n');
|
|
500
|
+
}
|
|
501
|
+
L.push((0, util_1.tabela)([
|
|
502
|
+
'PERFIL',
|
|
503
|
+
'THREAD',
|
|
504
|
+
'FASE',
|
|
505
|
+
'STATUS',
|
|
506
|
+
'BLOCO QUE PAUSA',
|
|
507
|
+
'AGUARDA DO HUMANO',
|
|
508
|
+
'IMPEDIMENTO',
|
|
509
|
+
'ULTIMO EVENTO',
|
|
510
|
+
'PARADA HA',
|
|
511
|
+
], m.linhas.map((l) => [
|
|
512
|
+
l.perfil,
|
|
513
|
+
l.thread,
|
|
514
|
+
l.faseAtual,
|
|
515
|
+
l.situacao,
|
|
516
|
+
l.pausas.map((p) => p.bloco || '?').join(' ') || '-',
|
|
517
|
+
l.pausas.map((p) => p.pausaSobre).filter((s) => s !== '').join('; ') || '-',
|
|
518
|
+
l.impedimentos.map((p) => p.motivo).join(' ') || 'nao',
|
|
519
|
+
l.ultimoEvento ? l.ultimoEvento.tipo : '(sem ledger)',
|
|
520
|
+
l.paradaHaMin === null
|
|
521
|
+
? '-'
|
|
522
|
+
: `${duracaoCurta(l.paradaHaMin)}${l.acimaDoLimite ? ' !' : ''}`,
|
|
523
|
+
])));
|
|
524
|
+
const hitl = m.linhas.filter((l) => l.precisaDeHumano);
|
|
525
|
+
if (hitl.length > 0) {
|
|
526
|
+
L.push('');
|
|
527
|
+
L.push('Esperando VEREDITO HUMANO agora (o orquestrador deve avisar o humano):');
|
|
528
|
+
for (const l of hitl) {
|
|
529
|
+
L.push(` ${l.thread} (${l.tag}, ${l.nome})`);
|
|
530
|
+
for (const p of l.pausas.filter((x) => x.sessaoViva !== true))
|
|
531
|
+
L.push(...linhaDaParada(p));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const impedidas = m.linhas.filter((l) => l.temImpedimento);
|
|
535
|
+
if (impedidas.length > 0) {
|
|
536
|
+
L.push('');
|
|
537
|
+
L.push('Com IMPEDIMENTO (nao e veredito humano: e colisao, gate, limite ou janela):');
|
|
538
|
+
for (const l of impedidas) {
|
|
539
|
+
L.push(` ${l.thread} (${l.tag}, ${l.nome})`);
|
|
540
|
+
for (const p of l.impedimentos)
|
|
541
|
+
L.push(...linhaDaParada(p));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const emCurso = m.linhas.filter((l) => l.pausas.some((p) => p.sessaoViva === true));
|
|
545
|
+
if (emCurso.length > 0) {
|
|
546
|
+
L.push('');
|
|
547
|
+
L.push('Pausa PREVISTA, sessao ainda viva (nao avise ainda, mas ja e a proxima fila):');
|
|
548
|
+
for (const l of emCurso) {
|
|
549
|
+
for (const p of l.pausas.filter((x) => x.sessaoViva === true)) {
|
|
550
|
+
L.push(` ${l.thread}: ${p.bloco || '?'} vai pausar sobre "${p.pausaSobre}"`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (m.resumo.aguardandoHumano === 0 && m.resumo.comImpedimento === 0) {
|
|
555
|
+
L.push('');
|
|
556
|
+
L.push(' Nenhuma thread parada: ninguem esperando veredito humano nem impedido.');
|
|
557
|
+
}
|
|
558
|
+
return L.join('\n');
|
|
559
|
+
}
|