@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,458 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `ork eval`: o runner de avaliacao do bloco B4.
|
|
4
|
+
*
|
|
5
|
+
* Ele julga duas coisas, e diz em voz alta que so uma delas ele consegue julgar.
|
|
6
|
+
*
|
|
7
|
+
* 1. **Os canarios** (`eval/fixtures/fx-*`): comportamento real do nucleo, exercitado ponta a
|
|
8
|
+
* ponta contra git de verdade, comparado com o que a fixture declara esperar.
|
|
9
|
+
* 2. **O corpus das skills** (`eval/casos/*.json`): as regras que precisam EXISTIR no arquivo
|
|
10
|
+
* da skill para o comportamento ser possivel. Isso e deterministico, e e o que o runner
|
|
11
|
+
* verifica: apague a regra da separacao no reviewer e o caso dele fica vermelho.
|
|
12
|
+
*
|
|
13
|
+
* O que o runner NAO julga e a metade comportamental de cada caso, aquela em que um agente e
|
|
14
|
+
* posto na situacao e alguem ve se ele resiste a desculpa. Essa metade sai `unavailable` em
|
|
15
|
+
* toda execucao, nunca `passing`. Regra herdada do catalogo original e mantida de proposito:
|
|
16
|
+
* **lacuna e publicada como lacuna**, porque um runner que chamasse isso de verde estaria
|
|
17
|
+
* medindo a propria vontade.
|
|
18
|
+
*
|
|
19
|
+
* Regra de catalogo que este runner torna executavel: skill sem eval nao entra, e eval sem
|
|
20
|
+
* skill tambem nao fica.
|
|
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.MINIMO_DE_CASOS = exports.TIPOS_DE_CASO = void 0;
|
|
57
|
+
exports.normalizar = normalizar;
|
|
58
|
+
exports.intervaloDaTabelaDeDesculpas = intervaloDaTabelaDeDesculpas;
|
|
59
|
+
exports.posicoesDaAssercao = posicoesDaAssercao;
|
|
60
|
+
exports.dirDeCasos = dirDeCasos;
|
|
61
|
+
exports.dirDeFixtures = dirDeFixtures;
|
|
62
|
+
exports.lerCasos = lerCasos;
|
|
63
|
+
exports.rodarCanarios = rodarCanarios;
|
|
64
|
+
exports.rodarEval = rodarEval;
|
|
65
|
+
exports.textoDoEval = textoDoEval;
|
|
66
|
+
const fs = __importStar(require("node:fs"));
|
|
67
|
+
const path = __importStar(require("node:path"));
|
|
68
|
+
const canarios_1 = require("./canarios");
|
|
69
|
+
const catalogo_1 = require("./catalogo");
|
|
70
|
+
const util_1 = require("./util");
|
|
71
|
+
/** Os tres tipos que toda skill precisa cobrir. Menos que isso e cobertura de fachada. */
|
|
72
|
+
exports.TIPOS_DE_CASO = ['caminho-feliz', 'racionalizacao', 'borda'];
|
|
73
|
+
/** Minimo de casos por skill, para o corpus nao virar uma linha por skill. */
|
|
74
|
+
exports.MINIMO_DE_CASOS = 3;
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// A tabela de racionalizacoes, e por que ela nao pode ser a unica ancora
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
/**
|
|
79
|
+
* Normaliza o texto da skill para casamento: toda sequencia de espaco em branco vira um
|
|
80
|
+
* espaco so.
|
|
81
|
+
*
|
|
82
|
+
* Sem isso, reflowar um paragrafo de markdown (mover uma palavra para a linha seguinte)
|
|
83
|
+
* deixaria uma assercao vermelha sem que regra nenhuma tivesse mudado. Um eval que reprova
|
|
84
|
+
* por causa de quebra de linha treina o time a ignorar o eval, que e o pior resultado
|
|
85
|
+
* possivel para um corpus de regressao.
|
|
86
|
+
*/
|
|
87
|
+
function normalizar(texto) {
|
|
88
|
+
return texto.replace(/\s+/g, ' ');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Intervalo da secao "Racionalizacoes comuns" dentro do texto JA NORMALIZADO da skill.
|
|
92
|
+
*
|
|
93
|
+
* Existe porque uma skill pode LISTAR a desculpa na tabela e nao ter, em lugar nenhum, a regra
|
|
94
|
+
* que responde a ela. O caso passaria, e a skill nao seguraria nada. Por isso todo caso de
|
|
95
|
+
* racionalizacao precisa de ao menos uma ancora FORA desta tabela.
|
|
96
|
+
*/
|
|
97
|
+
function intervaloDaTabelaDeDesculpas(texto) {
|
|
98
|
+
const normalizado = normalizar(texto);
|
|
99
|
+
const inicio = normalizado.indexOf('## Racionalizacoes comuns');
|
|
100
|
+
if (inicio < 0)
|
|
101
|
+
return null;
|
|
102
|
+
const resto = normalizado.indexOf('## ', inicio + 25);
|
|
103
|
+
return { inicio, fim: resto < 0 ? normalizado.length : resto };
|
|
104
|
+
}
|
|
105
|
+
/** Todas as posicoes em que a assercao casa no texto normalizado da skill. */
|
|
106
|
+
function posicoesDaAssercao(a, texto) {
|
|
107
|
+
const alvo = normalizar(texto);
|
|
108
|
+
const achadas = [];
|
|
109
|
+
if (a.tipo === 'casa') {
|
|
110
|
+
const regex = new RegExp(normalizar(a.valor), 'g');
|
|
111
|
+
let casado;
|
|
112
|
+
while ((casado = regex.exec(alvo)) !== null) {
|
|
113
|
+
achadas.push(casado.index);
|
|
114
|
+
if (casado[0].length === 0)
|
|
115
|
+
regex.lastIndex++;
|
|
116
|
+
}
|
|
117
|
+
return achadas;
|
|
118
|
+
}
|
|
119
|
+
const valor = normalizar(a.valor);
|
|
120
|
+
let de = alvo.indexOf(valor);
|
|
121
|
+
while (de >= 0) {
|
|
122
|
+
achadas.push(de);
|
|
123
|
+
de = alvo.indexOf(valor, de + 1);
|
|
124
|
+
}
|
|
125
|
+
return achadas;
|
|
126
|
+
}
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Corpus das skills
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
function dirDeCasos(catalogo) {
|
|
131
|
+
return path.join(catalogo, 'eval', 'casos');
|
|
132
|
+
}
|
|
133
|
+
function dirDeFixtures(catalogo) {
|
|
134
|
+
return path.join(catalogo, 'eval', 'fixtures');
|
|
135
|
+
}
|
|
136
|
+
/** Le os arquivos de caso do disco. Arquivo ilegivel e falha, nunca zero casos em silencio. */
|
|
137
|
+
function lerCasos(catalogo) {
|
|
138
|
+
const dir = dirDeCasos(catalogo);
|
|
139
|
+
const arquivos = [];
|
|
140
|
+
const falhas = [];
|
|
141
|
+
if (!fs.existsSync(dir))
|
|
142
|
+
return { arquivos, falhas };
|
|
143
|
+
for (const nome of fs.readdirSync(dir).sort()) {
|
|
144
|
+
if (!nome.endsWith('.json'))
|
|
145
|
+
continue;
|
|
146
|
+
const caminho = path.join(dir, nome);
|
|
147
|
+
try {
|
|
148
|
+
const lido = JSON.parse(fs.readFileSync(caminho, 'utf8'));
|
|
149
|
+
if (!lido.skill || !Array.isArray(lido.casos)) {
|
|
150
|
+
falhas.push({ codigo: 'fixture-invalida', detalhe: `${nome}: esperado { skill, casos[] }` });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
arquivos.push(lido);
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
falhas.push({ codigo: 'fixture-invalida', detalhe: `${nome}: ${e.message}` });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { arquivos, falhas };
|
|
160
|
+
}
|
|
161
|
+
function avaliarSkill(arquivo, textoDaSkill, vistos) {
|
|
162
|
+
const falhas = [];
|
|
163
|
+
const skill = arquivo.skill;
|
|
164
|
+
const tabelaDeDesculpas = intervaloDaTabelaDeDesculpas(textoDaSkill);
|
|
165
|
+
let assercoes = 0;
|
|
166
|
+
if (arquivo.casos.length < exports.MINIMO_DE_CASOS) {
|
|
167
|
+
falhas.push({
|
|
168
|
+
codigo: 'cobertura-minima',
|
|
169
|
+
skill,
|
|
170
|
+
detalhe: `${arquivo.casos.length} caso(s); o minimo do catalogo e ${exports.MINIMO_DE_CASOS}`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
for (const tipo of exports.TIPOS_DE_CASO) {
|
|
174
|
+
if (!arquivo.casos.some((c) => c.tipo === tipo)) {
|
|
175
|
+
falhas.push({ codigo: 'tipo-faltando', skill, detalhe: `nenhum caso do tipo "${tipo}"` });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const caso of arquivo.casos) {
|
|
179
|
+
if (vistos.has(caso.id)) {
|
|
180
|
+
falhas.push({ codigo: 'id-duplicado', skill, caso: caso.id, detalhe: 'id repetido no corpus' });
|
|
181
|
+
}
|
|
182
|
+
vistos.add(caso.id);
|
|
183
|
+
if (!caso.id.startsWith(`${skill}/`)) {
|
|
184
|
+
falhas.push({
|
|
185
|
+
codigo: 'id-sem-prefixo',
|
|
186
|
+
skill,
|
|
187
|
+
caso: caso.id,
|
|
188
|
+
detalhe: `o id precisa comecar com "${skill}/"`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (caso.tipo === 'racionalizacao' && !(caso.racionalizacao ?? '').trim()) {
|
|
192
|
+
falhas.push({
|
|
193
|
+
codigo: 'racionalizacao-sem-desculpa',
|
|
194
|
+
skill,
|
|
195
|
+
caso: caso.id,
|
|
196
|
+
detalhe: 'caso de racionalizacao sem o campo `racionalizacao` com a desculpa',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
let ancoraForaDaTabela = false;
|
|
200
|
+
for (const a of caso.assercoes) {
|
|
201
|
+
assercoes++;
|
|
202
|
+
if (!(a.porque ?? '').trim()) {
|
|
203
|
+
falhas.push({
|
|
204
|
+
codigo: 'assercao-sem-porque',
|
|
205
|
+
skill,
|
|
206
|
+
caso: caso.id,
|
|
207
|
+
detalhe: `assercao "${a.valor}" sem o campo \`porque\``,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
let posicoes;
|
|
211
|
+
try {
|
|
212
|
+
posicoes = posicoesDaAssercao(a, textoDaSkill);
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
falhas.push({
|
|
216
|
+
codigo: 'padrao-invalido',
|
|
217
|
+
skill,
|
|
218
|
+
caso: caso.id,
|
|
219
|
+
detalhe: `padrao "${a.valor}" invalido: ${e.message}`,
|
|
220
|
+
});
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (a.tipo === 'ausente') {
|
|
224
|
+
if (posicoes.length > 0) {
|
|
225
|
+
falhas.push({
|
|
226
|
+
codigo: 'assercao',
|
|
227
|
+
skill,
|
|
228
|
+
caso: caso.id,
|
|
229
|
+
detalhe: `"${a.valor}" deveria estar AUSENTE da skill e aparece ${posicoes.length}x`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (posicoes.length === 0) {
|
|
235
|
+
falhas.push({
|
|
236
|
+
codigo: 'assercao',
|
|
237
|
+
skill,
|
|
238
|
+
caso: caso.id,
|
|
239
|
+
detalhe: `"${a.valor}" nao existe em skills/.../${skill}/SKILL.md (${a.porque})`,
|
|
240
|
+
});
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!tabelaDeDesculpas ||
|
|
244
|
+
posicoes.some((p) => p < tabelaDeDesculpas.inicio || p >= tabelaDeDesculpas.fim)) {
|
|
245
|
+
ancoraForaDaTabela = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (caso.tipo === 'racionalizacao' && caso.assercoes.length > 0 && !ancoraForaDaTabela) {
|
|
249
|
+
falhas.push({
|
|
250
|
+
codigo: 'ancora-fora-da-tabela',
|
|
251
|
+
skill,
|
|
252
|
+
caso: caso.id,
|
|
253
|
+
detalhe: 'todas as ancoras deste caso estao dentro da tabela de racionalizacoes: a skill lista a desculpa mas nao carrega a regra que responde a ela',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
linha: { skill, casos: arquivo.casos.length, assercoes, falhas: falhas.length },
|
|
259
|
+
falhas,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function comparar(esperado, observado) {
|
|
263
|
+
const divergencias = [];
|
|
264
|
+
for (const [chave, valor] of Object.entries(esperado)) {
|
|
265
|
+
const veio = observado[chave];
|
|
266
|
+
if (JSON.stringify(veio) !== JSON.stringify(valor)) {
|
|
267
|
+
divergencias.push({ chave, esperado: valor, observado: veio });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return divergencias;
|
|
271
|
+
}
|
|
272
|
+
/** Roda os canarios cujas fixtures existem em `eval/fixtures/fx-*`. */
|
|
273
|
+
function rodarCanarios(catalogo, filtro) {
|
|
274
|
+
const dir = dirDeFixtures(catalogo);
|
|
275
|
+
const resultados = [];
|
|
276
|
+
const falhas = [];
|
|
277
|
+
const temGit = (0, canarios_1.gitDisponivel)();
|
|
278
|
+
const nomes = fs.existsSync(dir) ? fs.readdirSync(dir).sort() : [];
|
|
279
|
+
for (const nome of nomes) {
|
|
280
|
+
if (!nome.startsWith('fx-'))
|
|
281
|
+
continue;
|
|
282
|
+
if (filtro && !filtro.includes(nome))
|
|
283
|
+
continue;
|
|
284
|
+
const caminho = path.join(dir, nome, 'caso.json');
|
|
285
|
+
if (!fs.existsSync(caminho)) {
|
|
286
|
+
falhas.push({ codigo: 'fixture-invalida', detalhe: `${nome}: falta caso.json` });
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
let fixture;
|
|
290
|
+
try {
|
|
291
|
+
fixture = JSON.parse(fs.readFileSync(caminho, 'utf8'));
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
falhas.push({ codigo: 'fixture-invalida', detalhe: `${nome}: ${e.message}` });
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const canario = (0, canarios_1.canarioPorId)(fixture.id ?? nome);
|
|
298
|
+
if (!canario) {
|
|
299
|
+
falhas.push({
|
|
300
|
+
codigo: 'canario-sem-executor',
|
|
301
|
+
detalhe: `${nome}: nenhum executor registrado para o canario "${fixture.id ?? nome}"`,
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (canario.precisaDeGit && !temGit) {
|
|
306
|
+
resultados.push({
|
|
307
|
+
id: canario.id,
|
|
308
|
+
sobre: canario.sobre,
|
|
309
|
+
estado: 'unavailable',
|
|
310
|
+
divergencias: [],
|
|
311
|
+
motivo: 'git nao esta nesta maquina: o canario sai unavailable, nunca passing',
|
|
312
|
+
observado: {},
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const observado = canario.rodar({ catalogo });
|
|
318
|
+
const divergencias = comparar(fixture.esperado ?? {}, observado);
|
|
319
|
+
resultados.push({
|
|
320
|
+
id: canario.id,
|
|
321
|
+
sobre: canario.sobre,
|
|
322
|
+
estado: divergencias.length === 0 ? 'passou' : 'falhou',
|
|
323
|
+
divergencias,
|
|
324
|
+
motivo: divergencias.length === 0 ? '' : `${divergencias.length} chave(s) divergente(s)`,
|
|
325
|
+
observado,
|
|
326
|
+
});
|
|
327
|
+
for (const d of divergencias) {
|
|
328
|
+
falhas.push({
|
|
329
|
+
codigo: 'canario',
|
|
330
|
+
caso: `${canario.id}/${d.chave}`,
|
|
331
|
+
detalhe: `esperado ${JSON.stringify(d.esperado)}, observado ${JSON.stringify(d.observado)}`,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (e) {
|
|
336
|
+
resultados.push({
|
|
337
|
+
id: canario.id,
|
|
338
|
+
sobre: canario.sobre,
|
|
339
|
+
estado: 'falhou',
|
|
340
|
+
divergencias: [],
|
|
341
|
+
motivo: e.message,
|
|
342
|
+
observado: {},
|
|
343
|
+
});
|
|
344
|
+
falhas.push({ codigo: 'canario', caso: canario.id, detalhe: e.message });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Canario registrado no codigo e sem fixture em disco tambem e defeito: a expectativa
|
|
348
|
+
// precisa ser dado versionado, revisavel por quem nao le TypeScript.
|
|
349
|
+
if (!filtro) {
|
|
350
|
+
for (const c of canarios_1.CANARIOS) {
|
|
351
|
+
if (!fs.existsSync(path.join(dir, c.id, 'caso.json'))) {
|
|
352
|
+
falhas.push({
|
|
353
|
+
codigo: 'fixture-invalida',
|
|
354
|
+
caso: c.id,
|
|
355
|
+
detalhe: `o canario ${c.id} existe no codigo e nao tem eval/fixtures/${c.id}/caso.json`,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return { resultados, falhas };
|
|
361
|
+
}
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Orquestracao
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
/** Roda o eval inteiro: canarios mais corpus das skills. */
|
|
366
|
+
function rodarEval(opcoes) {
|
|
367
|
+
const { catalogo } = opcoes;
|
|
368
|
+
const falhas = [];
|
|
369
|
+
const linhas = [];
|
|
370
|
+
let canarios = [];
|
|
371
|
+
if (!opcoes.soSkills) {
|
|
372
|
+
const r = rodarCanarios(catalogo, opcoes.canarios);
|
|
373
|
+
canarios = r.resultados;
|
|
374
|
+
falhas.push(...r.falhas);
|
|
375
|
+
}
|
|
376
|
+
let totalDeCasos = 0;
|
|
377
|
+
let totalDeAssercoes = 0;
|
|
378
|
+
if (!opcoes.soCanarios) {
|
|
379
|
+
const { arquivos, falhas: falhasDeLeitura } = lerCasos(catalogo);
|
|
380
|
+
falhas.push(...falhasDeLeitura);
|
|
381
|
+
const doCatalogo = (0, catalogo_1.skillsDoCatalogo)(catalogo);
|
|
382
|
+
const comEval = new Set(arquivos.map((a) => a.skill));
|
|
383
|
+
const vistos = new Set();
|
|
384
|
+
for (const skill of doCatalogo) {
|
|
385
|
+
if (!comEval.has(skill.nome)) {
|
|
386
|
+
falhas.push({
|
|
387
|
+
codigo: 'skill-sem-eval',
|
|
388
|
+
skill: skill.nome,
|
|
389
|
+
detalhe: `skill sem eval nao entra no catalogo: falta eval/casos/${skill.nome}.json`,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const arquivo of arquivos) {
|
|
394
|
+
if (opcoes.skills && !opcoes.skills.includes(arquivo.skill))
|
|
395
|
+
continue;
|
|
396
|
+
const skill = doCatalogo.find((s) => s.nome === arquivo.skill);
|
|
397
|
+
if (!skill) {
|
|
398
|
+
falhas.push({
|
|
399
|
+
codigo: 'eval-sem-skill',
|
|
400
|
+
skill: arquivo.skill,
|
|
401
|
+
detalhe: 'ha casos para uma skill que nao existe no catalogo',
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const texto = fs.readFileSync(skill.caminho, 'utf8');
|
|
406
|
+
const avaliada = avaliarSkill(arquivo, texto, vistos);
|
|
407
|
+
linhas.push(avaliada.linha);
|
|
408
|
+
falhas.push(...avaliada.falhas);
|
|
409
|
+
totalDeCasos += avaliada.linha.casos;
|
|
410
|
+
totalDeAssercoes += avaliada.linha.assercoes;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
catalogo,
|
|
415
|
+
skills: linhas,
|
|
416
|
+
canarios,
|
|
417
|
+
falhas,
|
|
418
|
+
totalDeCasos,
|
|
419
|
+
totalDeAssercoes,
|
|
420
|
+
metadeComportamental: 'unavailable',
|
|
421
|
+
ok: falhas.length === 0,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/** Texto de `ork eval`. */
|
|
425
|
+
function textoDoEval(r) {
|
|
426
|
+
const linhas = [];
|
|
427
|
+
linhas.push(`ork eval, catalogo em ${r.catalogo}`);
|
|
428
|
+
linhas.push('');
|
|
429
|
+
if (r.canarios.length > 0) {
|
|
430
|
+
linhas.push('Canarios (comportamento real do nucleo, contra git de verdade)');
|
|
431
|
+
linhas.push('');
|
|
432
|
+
linhas.push((0, util_1.tabela)(['CANARIO', 'ESTADO', 'SOBRE'], r.canarios.map((c) => [c.id, c.estado, c.estado === 'passou' ? c.sobre : c.motivo || c.sobre])));
|
|
433
|
+
linhas.push('');
|
|
434
|
+
}
|
|
435
|
+
if (r.skills.length > 0) {
|
|
436
|
+
linhas.push('Corpus das skills (a metade estatica: as regras que precisam existir no arquivo)');
|
|
437
|
+
linhas.push('');
|
|
438
|
+
linhas.push((0, util_1.tabela)(['SKILL', 'CASOS', 'ASSERCOES', 'FALHAS'], r.skills.map((s) => [s.skill, String(s.casos), String(s.assercoes), String(s.falhas)])));
|
|
439
|
+
linhas.push('');
|
|
440
|
+
}
|
|
441
|
+
if (r.falhas.length > 0) {
|
|
442
|
+
linhas.push('Falhas');
|
|
443
|
+
for (const f of r.falhas) {
|
|
444
|
+
const onde = [f.skill, f.caso].filter(Boolean).join(' :: ');
|
|
445
|
+
linhas.push(` [${f.codigo}] ${onde}`);
|
|
446
|
+
linhas.push(` ${f.detalhe}`);
|
|
447
|
+
}
|
|
448
|
+
linhas.push('');
|
|
449
|
+
}
|
|
450
|
+
const passaram = r.canarios.filter((c) => c.estado === 'passou').length;
|
|
451
|
+
const indisponiveis = r.canarios.filter((c) => c.estado === 'unavailable').length;
|
|
452
|
+
linhas.push(`canarios: ${passaram}/${r.canarios.length} verdes` +
|
|
453
|
+
(indisponiveis > 0 ? ` (${indisponiveis} unavailable)` : ''));
|
|
454
|
+
linhas.push(`skills: ${r.skills.length} | casos: ${r.totalDeCasos} | assercoes: ${r.totalDeAssercoes} | falhas: ${r.falhas.length}`);
|
|
455
|
+
linhas.push('metade comportamental (o agente resiste a desculpa?): unavailable, julgada por humano ou por modelo, nunca por este runner');
|
|
456
|
+
linhas.push(r.ok ? 'VEREDITO: verde' : 'VEREDITO: vermelho');
|
|
457
|
+
return linhas.join('\n');
|
|
458
|
+
}
|