@orkastery/cli 0.4.3 → 0.5.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/adapters/README.md +11 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
- package/adapters/claude-code/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +2 -2
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/master.md +11 -8
- package/adapters/claude-code/commands/ork.md +8 -0
- package/adapters/claude-code/commands/ship.md +2 -2
- package/adapters/codex/skills/ork/SKILL.md +12 -1
- package/adapters/hermes/README.md +3 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
- package/adapters/hermes/bin/ork-brain.sh +3 -0
- package/adapters/hermes/bin/ork-maestro.sh +4 -1
- package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
- package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
- package/adapters/hermes/hermes.plugin.json +2 -0
- package/adapters/hermes/hitl-ingress/__init__.py +36 -3
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
- package/adapters/openclaw/README.md +34 -2
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +92 -20
- package/adapters/openclaw/openclaw.plugin.json +10 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +32 -3
- package/adapters/openclaw/src/index.ts +108 -20
- package/assets/docs/padroes/documentacao-de-produto.md +2 -2
- package/assets/docs/padroes/roadmap-de-produto.md +1 -1
- package/assets/docs/produto/_modelo-feature.md +2 -0
- package/assets/orkmind_bridge.py +214 -1
- package/dist/adapters/claude-bg.js +6 -3
- package/dist/board.js +74 -0
- package/dist/branch-de-estado.js +5 -4
- package/dist/busca-semantica.js +161 -0
- package/dist/ci.js +141 -10
- package/dist/company-brain-cli.js +27 -2
- package/dist/company-brain-context.js +132 -0
- package/dist/company-brain-dossie.js +376 -0
- package/dist/company-brain-mcp.js +4 -1
- package/dist/conducao.js +71 -18
- package/dist/decisao-autonoma.js +42 -0
- package/dist/docs.js +64 -22
- package/dist/doctor.js +33 -0
- package/dist/entrega-pr.js +109 -5
- package/dist/experiencia-instalacao.js +256 -0
- package/dist/experiencia.js +72 -0
- package/dist/fabrica-estado.js +43 -10
- package/dist/fechamento.js +105 -0
- package/dist/forja.js +435 -0
- package/dist/gates.js +9 -1
- package/dist/hitl-classificacao.js +11 -1
- package/dist/hitl-contract.js +50 -7
- package/dist/hitl-curto.js +158 -0
- package/dist/hitl-gates.js +67 -1
- package/dist/hitl-lote.js +30 -15
- package/dist/hitl-presentation.js +10 -7
- package/dist/hitl-resumo.js +33 -12
- package/dist/hitl-texto-livre.js +150 -0
- package/dist/hosts.js +86 -1
- package/dist/index.js +602 -38
- package/dist/indice-vetorial.js +317 -0
- package/dist/init.js +13 -0
- package/dist/integracoes-locais.js +1 -1
- package/dist/intelligence-benchmark-contract.js +570 -0
- package/dist/intelligence-graph-cli.js +430 -0
- package/dist/intelligence-graph-contract.js +577 -0
- package/dist/intelligence-graph-extract-md.js +469 -0
- package/dist/intelligence-graph-extract-ts.js +810 -0
- package/dist/intelligence-graph-extract.js +369 -0
- package/dist/intelligence-graph-index.js +512 -0
- package/dist/intelligence-graph-parsers.js +327 -0
- package/dist/intelligence-graph-query.js +344 -0
- package/dist/intelligence-graph-repo.js +187 -0
- package/dist/leases.js +84 -0
- package/dist/ledger.js +14 -0
- package/dist/maestro-cli.js +10 -1
- package/dist/maestro-contract.js +5 -1
- package/dist/maestro-discovery.js +16 -2
- package/dist/maestro-snapshot.js +6 -2
- package/dist/manifest.js +142 -5
- package/dist/master-batch.js +3 -2
- package/dist/master-digest.js +2 -2
- package/dist/master-nota.js +203 -0
- package/dist/master.js +4 -0
- package/dist/mcp-experiencia.js +164 -0
- package/dist/mcp-git.js +4 -0
- package/dist/mcp-install.js +3 -1
- package/dist/mcp-maestro.js +3 -2
- package/dist/mcp-server.js +84 -5
- package/dist/memoria.js +147 -1
- package/dist/network-roadmap.js +907 -0
- package/dist/ocupacao.js +38 -2
- package/dist/onboarding.js +113 -7
- package/dist/orkmind.js +165 -9
- package/dist/phase.js +166 -13
- package/dist/policies.js +2 -0
- package/dist/projeto-alvo.js +551 -0
- package/dist/prompts.js +1 -1
- package/dist/pulse-consentimento.js +4 -2
- package/dist/pulse-delivery.js +5 -0
- package/dist/pulse-resposta.js +674 -49
- package/dist/pulse.js +21 -0
- package/dist/retry.js +51 -8
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +213 -0
- package/dist/thread-close.js +3 -0
- package/dist/verify.js +4 -3
- package/eval/casos/orchestration-experience-pt-br.json +48 -0
- package/eval/casos/orchestration-experience.json +48 -0
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +581 -0
- package/schemas/graph-benchmark.v1.schema.json +2240 -0
- package/schemas/maestro-snapshot.schema.json +23 -0
- package/skills/README.md +7 -5
- package/skills/core/onboarding/SKILL.md +7 -0
- package/skills/core/orchestration-experience/SKILL.md +113 -0
- package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/go-implementation/SKILL.md +2 -0
package/dist/forja.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mesmaForja = exports.rotuloDaForja = exports.executorPadrao = void 0;
|
|
4
|
+
exports.identidadeDaForja = identidadeDaForja;
|
|
5
|
+
exports.forjaDoArgumento = forjaDoArgumento;
|
|
6
|
+
exports.detalheSeguro = detalheSeguro;
|
|
7
|
+
exports.classificarFalha = classificarFalha;
|
|
8
|
+
exports.lerCommitsDaForja = lerCommitsDaForja;
|
|
9
|
+
exports.lerDaForja = lerDaForja;
|
|
10
|
+
/**
|
|
11
|
+
* RM-054 (fatia 1): a forja do projeto, lida sem clone e so com consulta.
|
|
12
|
+
*
|
|
13
|
+
* `ork/fabrica-estado` e `ork/roadmap-reservas` vivem no remoto de UM repositorio, e um host fora
|
|
14
|
+
* de um clone (o gateway do OpenClaw, um cron) nao enxergava nenhuma das duas. Aqui a leitura vai
|
|
15
|
+
* direto a forja, pela CLI dela ja autenticada (`gh`, `glab`): nenhum token e lido, copiado ou
|
|
16
|
+
* passado em argumento, e so consulta sai daqui (GraphQL sem mutation e GET no REST). O corpo da
|
|
17
|
+
* consulta vai pelo stdin, com os nomes em variaveis: nada do pedido e interpolado no texto.
|
|
18
|
+
*
|
|
19
|
+
* GitHub responde tudo em UMA consulta: a base com `docs/roadmap`, o manifesto e os commits desde
|
|
20
|
+
* `desde`, e as duas branches de estado, cada parte no commit que a propria consulta devolve.
|
|
21
|
+
* GitLab devolve a mesma leitura por consultas GraphQL e pelo REST de commits; sem `glab` na
|
|
22
|
+
* maquina que construiu, o esquema dele esta provado so com resposta simulada (D4 do PLAN).
|
|
23
|
+
*
|
|
24
|
+
* Resposta fora do formato vira erro tipado, nunca lista vazia: quem le nao confunde "nao li" com
|
|
25
|
+
* "nao tem".
|
|
26
|
+
*/
|
|
27
|
+
const node_child_process_1 = require("node:child_process");
|
|
28
|
+
const hitl_1 = require("./hitl");
|
|
29
|
+
const policies_1 = require("./policies");
|
|
30
|
+
const PRAZO_MS = 30000;
|
|
31
|
+
const SHA = /^[0-9a-f]{40}$/;
|
|
32
|
+
const SEGMENTO = /^[A-Za-z0-9_.-]+$/;
|
|
33
|
+
/** Hostname de verdade: rotulos alfanumericos separados por ponto (`..` ou `a..b` nao passam). */
|
|
34
|
+
const HOST = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;
|
|
35
|
+
/** Sem shell, sem token: a CLI usa a autenticacao que ja tem; prompt e aviso de versao desligados. */
|
|
36
|
+
const executorPadrao = (cmd, args, entrada, timeoutMs) => {
|
|
37
|
+
const r = (0, node_child_process_1.spawnSync)(cmd, [...args], {
|
|
38
|
+
input: entrada, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024,
|
|
39
|
+
env: { ...process.env, GH_PROMPT_DISABLED: '1', GH_NO_UPDATE_NOTIFIER: '1', GLAB_CHECK_UPDATE: 'false', NO_COLOR: '1' },
|
|
40
|
+
});
|
|
41
|
+
return { status: r.status, stdout: (r.stdout ?? '').toString(), stderr: (r.stderr ?? '').toString(), sinal: r.signal ?? null,
|
|
42
|
+
...(r.error ? { erro: r.error.code ?? r.error.message } : {}) };
|
|
43
|
+
};
|
|
44
|
+
exports.executorPadrao = executorPadrao;
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Identidade
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
function tipoDoHost(host) {
|
|
49
|
+
if (host === 'github.com' || /(^|[.-])github([.-]|$)/.test(host))
|
|
50
|
+
return 'github';
|
|
51
|
+
if (host === 'gitlab.com' || /(^|[.-])gitlab([.-]|$)/.test(host))
|
|
52
|
+
return 'gitlab';
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function montar(tipo, host, caminho) {
|
|
56
|
+
if (!HOST.test(host))
|
|
57
|
+
return null;
|
|
58
|
+
const partes = caminho.replace(/\/+$/, '').replace(/\.git$/, '').split('/');
|
|
59
|
+
if (partes.some((p) => !SEGMENTO.test(p) || p === '.' || p === '..'))
|
|
60
|
+
return null;
|
|
61
|
+
if (tipo === 'github' ? partes.length !== 2 : partes.length < 2)
|
|
62
|
+
return null;
|
|
63
|
+
return { tipo, host, repo: partes.join('/') };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A forja de uma URL de remoto (https, ssh ou scp). Credencial na URL nunca entra na identidade;
|
|
67
|
+
* caminho local e forja desconhecida voltam null.
|
|
68
|
+
*/
|
|
69
|
+
function identidadeDaForja(url) {
|
|
70
|
+
const t = url.trim();
|
|
71
|
+
const esquema = /^(?:https?|ssh|git|git\+ssh|ssh\+git):\/\/(?:[^@/]*@)?([^/:?#@]+)(?::\d+)?\/([^?#\s]+)$/i.exec(t);
|
|
72
|
+
const scp = esquema ? null : /^(?:[^@\s/:]+@)?([^:\s/@]+):(?!\/\/)([^\s]+)$/.exec(t);
|
|
73
|
+
const achado = esquema ?? scp;
|
|
74
|
+
if (!achado)
|
|
75
|
+
return null;
|
|
76
|
+
const host = achado[1].toLowerCase();
|
|
77
|
+
const tipo = tipoDoHost(host);
|
|
78
|
+
return tipo ? montar(tipo, host, achado[2].replace(/^\/+/, '')) : null;
|
|
79
|
+
}
|
|
80
|
+
/** `github:dono/repo`, `gitlab:grupo/sub/repo` (com host proprio: `gitlab:host.tld/grupo/repo`) ou uma URL. */
|
|
81
|
+
function forjaDoArgumento(texto) {
|
|
82
|
+
const t = texto.trim();
|
|
83
|
+
const m = /^(github|gitlab):(?:\/\/)?(.+)$/i.exec(t);
|
|
84
|
+
if (m) {
|
|
85
|
+
const tipo = m[1].toLowerCase();
|
|
86
|
+
const partes = m[2].replace(/^\/+/, '').split('/');
|
|
87
|
+
const comHost = partes.length > 2 && partes[0].includes('.');
|
|
88
|
+
const host = comHost ? partes[0].toLowerCase() : tipo === 'github' ? 'github.com' : 'gitlab.com';
|
|
89
|
+
return montar(tipo, host, (comHost ? partes.slice(1) : partes).join('/'));
|
|
90
|
+
}
|
|
91
|
+
return /^[a-z+]+:\/\//i.test(t) || /^[^@\s/:]+@[^:\s/]+:/.test(t) ? identidadeDaForja(t) : null;
|
|
92
|
+
}
|
|
93
|
+
/** Como a forja aparece para gente: `github.com/orkastery/orkastery`. */
|
|
94
|
+
const rotuloDaForja = (f) => `${f.host}/${f.repo}`;
|
|
95
|
+
exports.rotuloDaForja = rotuloDaForja;
|
|
96
|
+
const mesmaForja = (a, b) => !!a && !!b && a.tipo === b.tipo && a.host === b.host && a.repo.toLowerCase() === b.repo.toLowerCase();
|
|
97
|
+
exports.mesmaForja = mesmaForja;
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Erros
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
/** O fim da saida de erro, sem segredo: redigido, e descartado inteiro se ainda casar um padrao. */
|
|
102
|
+
function detalheSeguro(texto) {
|
|
103
|
+
const redigido = (0, hitl_1.redigirSegredos)(texto)
|
|
104
|
+
.replace(/\b(?:glpat|gldt|glrt|glptt|glcbt)-[A-Za-z0-9_-]{16,}\b/g, '[redigido]')
|
|
105
|
+
.replace(/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, '[redigido]');
|
|
106
|
+
const linha = redigido.split('\n').map((l) => l.trim()).filter(Boolean).slice(-2).join(' ').slice(0, 240);
|
|
107
|
+
return (0, policies_1.procurarSegredos)(linha).length ? '[detalhe omitido: um padrão de segredo casou]' : linha;
|
|
108
|
+
}
|
|
109
|
+
function erro(codigo, detalhe) {
|
|
110
|
+
return { ok: false, erro: { codigo, detalhe } };
|
|
111
|
+
}
|
|
112
|
+
/** O motivo tipado de uma chamada que falhou. */
|
|
113
|
+
function classificarFalha(cmd, s) {
|
|
114
|
+
if (s.erro === 'ENOENT')
|
|
115
|
+
return { codigo: 'forja.ausente', detalhe: `${cmd} não está instalado nesta máquina` };
|
|
116
|
+
if (s.erro === 'ETIMEDOUT' || s.sinal === 'SIGTERM')
|
|
117
|
+
return { codigo: 'forja.tempo-esgotado', detalhe: `${cmd} não respondeu em ${PRAZO_MS / 1000} s` };
|
|
118
|
+
const texto = `${s.stderr}\n${s.stdout}`;
|
|
119
|
+
const detalhe = detalheSeguro(s.stderr || s.stdout || s.erro || `${cmd} saiu com ${s.status}`);
|
|
120
|
+
if (/"type"\s*:\s*"NOT_FOUND"|Could not resolve to a Repository|404 Not Found|HTTP 404|project not found/i.test(texto)) {
|
|
121
|
+
return { codigo: 'forja.nao-encontrado', detalhe };
|
|
122
|
+
}
|
|
123
|
+
if (/auth login|not logged|authentication|HTTP 401|Bad credentials|GH_TOKEN|GITLAB_TOKEN|unauthorized|no token/i.test(texto)) {
|
|
124
|
+
return { codigo: 'forja.sem-login', detalhe };
|
|
125
|
+
}
|
|
126
|
+
return { codigo: 'forja.inacessivel', detalhe };
|
|
127
|
+
}
|
|
128
|
+
const obj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
129
|
+
class RespostaInvalida extends Error {
|
|
130
|
+
}
|
|
131
|
+
class NaoEncontrado extends Error {
|
|
132
|
+
}
|
|
133
|
+
const exigir = (condicao, onde) => { if (!condicao)
|
|
134
|
+
throw new RespostaInvalida(onde); };
|
|
135
|
+
function chamarJson(executor, cmd, args, corpo) {
|
|
136
|
+
const s = executor(cmd, args, corpo, PRAZO_MS);
|
|
137
|
+
let json;
|
|
138
|
+
try {
|
|
139
|
+
json = JSON.parse(s.stdout);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
json = undefined;
|
|
143
|
+
}
|
|
144
|
+
const erros = obj(json) && Array.isArray(json.errors) && json.errors.length > 0;
|
|
145
|
+
if (s.erro || s.status !== 0 || erros) {
|
|
146
|
+
const falha = classificarFalha(cmd, s.erro || s.status !== 0 ? s : { ...s, status: 1, stderr: JSON.stringify(json) });
|
|
147
|
+
// `gh` sai 1 com NOT_FOUND no corpo: o tipo do erro GraphQL vale mais que a frase.
|
|
148
|
+
if (erros && json.errors.some((e) => obj(e) && e.type === 'NOT_FOUND')) {
|
|
149
|
+
return { ok: false, erro: { codigo: 'forja.nao-encontrado', detalhe: falha.detalhe } };
|
|
150
|
+
}
|
|
151
|
+
return { ok: false, erro: falha };
|
|
152
|
+
}
|
|
153
|
+
if (json === undefined)
|
|
154
|
+
return { ok: false, erro: { codigo: 'forja.resposta-invalida', detalhe: `${cmd} não devolveu JSON` } };
|
|
155
|
+
return { ok: true, json };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* O host vai sempre explicito: sem ele, o `gh` usa `GH_HOST` ou o unico host com login, e o `glab`,
|
|
159
|
+
* o host do remoto do cwd; a consulta iria para outra forja.
|
|
160
|
+
*/
|
|
161
|
+
const hostnameDe = (f) => ['--hostname', f.host];
|
|
162
|
+
function arquivosDoGithub(entrada, dir) {
|
|
163
|
+
if (entrada === null)
|
|
164
|
+
return null;
|
|
165
|
+
exigir(obj(entrada), `${dir}: entrada`);
|
|
166
|
+
const objeto = entrada.object;
|
|
167
|
+
if (objeto === null)
|
|
168
|
+
return null;
|
|
169
|
+
exigir(obj(objeto) && Array.isArray(objeto.entries), `${dir}: diretorio`);
|
|
170
|
+
return objeto.entries.filter((e) => obj(e) && e.type === 'blob').map((e) => {
|
|
171
|
+
const x = e, blob = obj(x.object) ? x.object : {};
|
|
172
|
+
exigir(typeof x.name === 'string', `${dir}: nome`);
|
|
173
|
+
const legivel = typeof blob.text === 'string' && blob.isBinary !== true && blob.isTruncated !== true;
|
|
174
|
+
return { caminho: `${dir}/${x.name}`, texto: legivel ? blob.text : null };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function pontaDoGithub(ref, onde) {
|
|
178
|
+
if (ref === null)
|
|
179
|
+
return null;
|
|
180
|
+
exigir(obj(ref) && obj(ref.target), `${onde}: ref`);
|
|
181
|
+
const alvo = ref.target;
|
|
182
|
+
exigir(typeof alvo.oid === 'string' && SHA.test(alvo.oid), `${onde}: commit`);
|
|
183
|
+
return { commit: alvo.oid, dataDoCommit: typeof alvo.committedDate === 'string' ? alvo.committedDate : null,
|
|
184
|
+
nome: typeof ref.name === 'string' ? ref.name : onde, alvo };
|
|
185
|
+
}
|
|
186
|
+
/** Os commits de um `history` do GitHub, e se a forja cortou a pagina (mais de 100 na janela). */
|
|
187
|
+
function commitsDoHistorico(historico, onde) {
|
|
188
|
+
const h = obj(historico) ? historico : {};
|
|
189
|
+
const nos = h.nodes ?? [];
|
|
190
|
+
exigir(Array.isArray(nos), `${onde}: commits`);
|
|
191
|
+
return {
|
|
192
|
+
commits: nos.filter(obj).map((n) => {
|
|
193
|
+
exigir(typeof n.oid === 'string' && typeof n.messageHeadline === 'string' && typeof n.committedDate === 'string', `${onde}: commit`);
|
|
194
|
+
return { commit: n.oid, assunto: n.messageHeadline, data: n.committedDate };
|
|
195
|
+
}),
|
|
196
|
+
parciais: obj(h.pageInfo) && h.pageInfo.hasNextPage === true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Os commits da base desde `desde`, pelo REST do GitLab (GET), e se vieram 100 (pagina cheia). */
|
|
200
|
+
function commitsDoGitlab(forja, commit, desde, executor) {
|
|
201
|
+
const rest = executor('glab', ['api', `projects/${encodeURIComponent(forja.repo)}/repository/commits?ref_name=${encodeURIComponent(commit)}` +
|
|
202
|
+
`&since=${encodeURIComponent(desde)}&per_page=100`, ...hostnameDe(forja)], '', PRAZO_MS);
|
|
203
|
+
if (rest.erro || rest.status !== 0)
|
|
204
|
+
return { ok: false, erro: classificarFalha('glab', rest) };
|
|
205
|
+
let lista;
|
|
206
|
+
try {
|
|
207
|
+
lista = JSON.parse(rest.stdout);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
lista = undefined;
|
|
211
|
+
}
|
|
212
|
+
exigir(Array.isArray(lista), 'commits');
|
|
213
|
+
const commits = lista.map((c) => {
|
|
214
|
+
exigir(obj(c) && typeof c.id === 'string' && typeof c.title === 'string' && typeof c.committed_date === 'string', 'commit');
|
|
215
|
+
return { commit: c.id, assunto: c.title, data: c.committed_date };
|
|
216
|
+
});
|
|
217
|
+
return { ok: true, commits, parciais: commits.length >= 100 };
|
|
218
|
+
}
|
|
219
|
+
function consultaDoGithub(comBase) {
|
|
220
|
+
return `query($owner: String!, $name: String!${comBase ? ', $base: String!' : ''}, $desde: GitTimestamp!, $dirRoadmap: String!,
|
|
221
|
+
$manifesto: String!, $refReservas: String!, $dirReservas: String!, $refFabrica: String!, $dirFabrica: String!) {
|
|
222
|
+
repository(owner: $owner, name: $name) {
|
|
223
|
+
base: ${comBase ? 'ref(qualifiedName: $base)' : 'defaultBranchRef'} { name target { ... on Commit { oid committedDate
|
|
224
|
+
roadmap: file(path: $dirRoadmap) { object { ...Arquivos } }
|
|
225
|
+
manifesto: file(path: $manifesto) { object { ... on Blob { text isBinary isTruncated } } }
|
|
226
|
+
history(since: $desde, first: 100) { pageInfo { hasNextPage } nodes { oid messageHeadline committedDate } } } } }
|
|
227
|
+
reservas: ref(qualifiedName: $refReservas) { name target { ... on Commit { oid committedDate
|
|
228
|
+
arquivos: file(path: $dirReservas) { object { ...Arquivos } } } } }
|
|
229
|
+
fabrica: ref(qualifiedName: $refFabrica) { name target { ... on Commit { oid committedDate
|
|
230
|
+
arquivos: file(path: $dirFabrica) { object { ...Arquivos } } } } }
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
fragment Arquivos on Tree { entries { name type object { ... on Blob { text isBinary isTruncated } } } }`;
|
|
234
|
+
}
|
|
235
|
+
function lerDoGithub(forja, pedido, executor, lidoEm) {
|
|
236
|
+
const [owner, name] = forja.repo.split('/');
|
|
237
|
+
const comBase = !!pedido.base;
|
|
238
|
+
const corpo = JSON.stringify({ query: consultaDoGithub(comBase), variables: {
|
|
239
|
+
owner, name, ...(comBase ? { base: `refs/heads/${pedido.base}` } : {}), desde: pedido.desde,
|
|
240
|
+
dirRoadmap: pedido.dirRoadmap, manifesto: pedido.manifesto,
|
|
241
|
+
refReservas: `refs/heads/${pedido.reservas.branch}`, dirReservas: pedido.reservas.dir,
|
|
242
|
+
refFabrica: `refs/heads/${pedido.fabrica.branch}`, dirFabrica: pedido.fabrica.dir,
|
|
243
|
+
} });
|
|
244
|
+
const r = chamarJson(executor, 'gh', ['api', 'graphql', '--method', 'POST', '--input', '-', ...hostnameDe(forja)], corpo);
|
|
245
|
+
if (!r.ok)
|
|
246
|
+
return r;
|
|
247
|
+
const dados = obj(r.json) ? r.json.data : undefined;
|
|
248
|
+
exigir(obj(dados) && obj(dados.repository), 'repositorio');
|
|
249
|
+
const repo = dados.repository;
|
|
250
|
+
const base = pontaDoGithub(repo.base ?? null, 'base');
|
|
251
|
+
let leituraDaBase = null;
|
|
252
|
+
if (base) {
|
|
253
|
+
const manifesto = obj(base.alvo.manifesto) && obj(base.alvo.manifesto.object) &&
|
|
254
|
+
typeof base.alvo.manifesto.object.text === 'string' ? base.alvo.manifesto.object.text : null;
|
|
255
|
+
const historico = commitsDoHistorico(base.alvo.history, 'base');
|
|
256
|
+
leituraDaBase = { ref: base.nome, commit: base.commit, dataDoCommit: base.dataDoCommit,
|
|
257
|
+
arquivos: arquivosDoGithub(base.alvo.roadmap ?? null, pedido.dirRoadmap), parcial: false, manifesto,
|
|
258
|
+
commits: historico.commits, commitsParciais: historico.parciais };
|
|
259
|
+
}
|
|
260
|
+
const estado = (bruto, b) => {
|
|
261
|
+
const p = pontaDoGithub(bruto ?? null, b.branch);
|
|
262
|
+
return p ? { ref: b.branch, commit: p.commit, dataDoCommit: p.dataDoCommit, arquivos: arquivosDoGithub(p.alvo.arquivos ?? null, b.dir),
|
|
263
|
+
parcial: false } : null;
|
|
264
|
+
};
|
|
265
|
+
return { ok: true, leitura: { forja, lidoEm, chamadas: 1, base: leituraDaBase,
|
|
266
|
+
reservas: estado(repo.reservas, pedido.reservas), fabrica: estado(repo.fabrica, pedido.fabrica) } };
|
|
267
|
+
}
|
|
268
|
+
const CONSULTA_RAIZ_GITLAB = 'query($path: ID!) { project(fullPath: $path) { repository { rootRef } } }';
|
|
269
|
+
const CONSULTA_ARVORES_GITLAB = `query($path: ID!, $base: String!, $dirRoadmap: String!, $refReservas: String!, $dirReservas: String!,
|
|
270
|
+
$refFabrica: String!, $dirFabrica: String!) {
|
|
271
|
+
project(fullPath: $path) { repository {
|
|
272
|
+
baseTopo: tree(ref: $base) { lastCommit { sha committedDate } }
|
|
273
|
+
baseDir: tree(ref: $base, path: $dirRoadmap) { blobs(first: 100) { pageInfo { hasNextPage } nodes { name path } } }
|
|
274
|
+
reservasTopo: tree(ref: $refReservas) { lastCommit { sha committedDate } }
|
|
275
|
+
reservasDir: tree(ref: $refReservas, path: $dirReservas) { blobs(first: 100) { pageInfo { hasNextPage } nodes { name path } } }
|
|
276
|
+
fabricaTopo: tree(ref: $refFabrica) { lastCommit { sha committedDate } }
|
|
277
|
+
fabricaDir: tree(ref: $refFabrica, path: $dirFabrica) { blobs(first: 100) { pageInfo { hasNextPage } nodes { name path } } }
|
|
278
|
+
} }
|
|
279
|
+
}`;
|
|
280
|
+
/** Os blobs de cada ponta que existe; so as partes pedidas entram na consulta (texto fixo, sem interpolar o pedido). */
|
|
281
|
+
function consultaDeBlobsDoGitlab(partes) {
|
|
282
|
+
const sufixo = { base: 'Base', reservas: 'Reservas', fabrica: 'Fabrica' };
|
|
283
|
+
const declaracoes = ['$path: ID!', ...partes.flatMap((p) => [`$ref${sufixo[p]}: String!`, `$paths${sufixo[p]}: [String!]!`])];
|
|
284
|
+
const campos = partes.map((p) => `${p}: blobs(ref: $ref${sufixo[p]}, paths: $paths${sufixo[p]}, first: 100) ` +
|
|
285
|
+
'{ pageInfo { hasNextPage } nodes { path rawTextBlob } }');
|
|
286
|
+
return `query(${declaracoes.join(', ')}) { project(fullPath: $path) { repository { ${campos.join(' ')} } } }`;
|
|
287
|
+
}
|
|
288
|
+
function lerDoGitlab(forja, pedido, executor, lidoEm) {
|
|
289
|
+
const args = ['api', 'graphql', '--method', 'POST', '--input', '-', ...hostnameDe(forja)];
|
|
290
|
+
let chamadas = 0;
|
|
291
|
+
const graphql = (query, variables) => { chamadas++; return chamarJson(executor, 'glab', args, JSON.stringify({ query, variables })); };
|
|
292
|
+
const repositorio = (json) => {
|
|
293
|
+
const projeto = obj(json) && obj(json.data) ? json.data.project : undefined;
|
|
294
|
+
if (projeto === null)
|
|
295
|
+
throw new NaoEncontrado();
|
|
296
|
+
exigir(obj(projeto) && obj(projeto.repository), 'repositorio');
|
|
297
|
+
return projeto.repository;
|
|
298
|
+
};
|
|
299
|
+
let base = pedido.base ?? null;
|
|
300
|
+
if (!base) {
|
|
301
|
+
const r = graphql(CONSULTA_RAIZ_GITLAB, { path: forja.repo });
|
|
302
|
+
if (!r.ok)
|
|
303
|
+
return r;
|
|
304
|
+
const raiz = repositorio(r.json).rootRef;
|
|
305
|
+
base = typeof raiz === 'string' && raiz ? raiz : null;
|
|
306
|
+
}
|
|
307
|
+
const arvores = graphql(CONSULTA_ARVORES_GITLAB, { path: forja.repo, base: base ?? 'HEAD', dirRoadmap: pedido.dirRoadmap,
|
|
308
|
+
refReservas: pedido.reservas.branch, dirReservas: pedido.reservas.dir, refFabrica: pedido.fabrica.branch, dirFabrica: pedido.fabrica.dir });
|
|
309
|
+
if (!arvores.ok)
|
|
310
|
+
return arvores;
|
|
311
|
+
const repo = repositorio(arvores.json);
|
|
312
|
+
const topo = (chave) => {
|
|
313
|
+
const arvore = repo[chave];
|
|
314
|
+
if (!obj(arvore) || arvore.lastCommit === null || arvore.lastCommit === undefined)
|
|
315
|
+
return null;
|
|
316
|
+
const c = arvore.lastCommit;
|
|
317
|
+
exigir(obj(c) && typeof c.sha === 'string' && SHA.test(c.sha), `${chave}: commit`);
|
|
318
|
+
return { commit: c.sha, dataDoCommit: typeof c.committedDate === 'string' ? c.committedDate : null };
|
|
319
|
+
};
|
|
320
|
+
const cortadas = new Set();
|
|
321
|
+
const cortou = (conexao, chave) => {
|
|
322
|
+
if (obj(conexao.pageInfo) && conexao.pageInfo.hasNextPage === true)
|
|
323
|
+
cortadas.add(chave);
|
|
324
|
+
};
|
|
325
|
+
const caminhos = (chave) => {
|
|
326
|
+
const arvore = repo[chave];
|
|
327
|
+
if (!obj(arvore))
|
|
328
|
+
return null;
|
|
329
|
+
exigir(obj(arvore.blobs) && Array.isArray(arvore.blobs.nodes), `${chave}: blobs`);
|
|
330
|
+
cortou(arvore.blobs, chave);
|
|
331
|
+
return arvore.blobs.nodes.map((n) => {
|
|
332
|
+
exigir(obj(n) && typeof n.path === 'string', `${chave}: caminho`);
|
|
333
|
+
return n.path;
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
const [pontaBase, pontaReservas, pontaFabrica] = [base ? topo('baseTopo') : null, topo('reservasTopo'), topo('fabricaTopo')];
|
|
337
|
+
const dirBase = pontaBase ? caminhos('baseDir') : null;
|
|
338
|
+
const dirReservas = pontaReservas ? caminhos('reservasDir') : null, dirFabrica = pontaFabrica ? caminhos('fabricaDir') : null;
|
|
339
|
+
const pedidos = [
|
|
340
|
+
['base', pontaBase, pontaBase ? [...(dirBase ?? []), pedido.manifesto] : []],
|
|
341
|
+
['reservas', pontaReservas, dirReservas ?? []],
|
|
342
|
+
['fabrica', pontaFabrica, dirFabrica ?? []],
|
|
343
|
+
].filter(([, ponta, paths]) => ponta && paths.length > 0);
|
|
344
|
+
let conteudo = {};
|
|
345
|
+
if (pedidos.length) {
|
|
346
|
+
const sufixo = { base: 'Base', reservas: 'Reservas', fabrica: 'Fabrica' };
|
|
347
|
+
const variaveis = { path: forja.repo };
|
|
348
|
+
for (const [parte, ponta, paths] of pedidos) {
|
|
349
|
+
variaveis[`ref${sufixo[parte]}`] = ponta.commit;
|
|
350
|
+
variaveis[`paths${sufixo[parte]}`] = paths;
|
|
351
|
+
}
|
|
352
|
+
const blobs = graphql(consultaDeBlobsDoGitlab(pedidos.map(([parte]) => parte)), variaveis);
|
|
353
|
+
if (!blobs.ok)
|
|
354
|
+
return blobs;
|
|
355
|
+
conteudo = repositorio(blobs.json);
|
|
356
|
+
}
|
|
357
|
+
const textos = (chave) => {
|
|
358
|
+
const c = conteudo[chave];
|
|
359
|
+
if (c === null || c === undefined)
|
|
360
|
+
return new Map();
|
|
361
|
+
exigir(obj(c) && Array.isArray(c.nodes), `${chave}: conteudo`);
|
|
362
|
+
cortou(c, chave);
|
|
363
|
+
return new Map(c.nodes.map((n) => {
|
|
364
|
+
exigir(obj(n) && typeof n.path === 'string', `${chave}: blob`);
|
|
365
|
+
return [n.path, typeof n.rawTextBlob === 'string' ? n.rawTextBlob : null];
|
|
366
|
+
}));
|
|
367
|
+
};
|
|
368
|
+
const arquivos = (lista, mapa) => lista === null ? null : lista.map((caminho) => ({ caminho, texto: mapa.get(caminho) ?? null }));
|
|
369
|
+
let commits = [];
|
|
370
|
+
let commitsParciais = false;
|
|
371
|
+
if (pontaBase) {
|
|
372
|
+
chamadas++;
|
|
373
|
+
const r = commitsDoGitlab(forja, pontaBase.commit, pedido.desde, executor);
|
|
374
|
+
if (!r.ok)
|
|
375
|
+
return r;
|
|
376
|
+
commits = r.commits;
|
|
377
|
+
commitsParciais = r.parciais;
|
|
378
|
+
}
|
|
379
|
+
const [mapaBase, mapaReservas, mapaFabrica] = [textos('base'), textos('reservas'), textos('fabrica')];
|
|
380
|
+
const parcial = (dir, conteudo) => cortadas.has(dir) || cortadas.has(conteudo);
|
|
381
|
+
return { ok: true, leitura: { forja, lidoEm, chamadas,
|
|
382
|
+
base: pontaBase ? { ref: base ?? 'HEAD', ...pontaBase, arquivos: arquivos(dirBase, mapaBase), parcial: parcial('baseDir', 'base'),
|
|
383
|
+
manifesto: mapaBase.get(pedido.manifesto) ?? null, commits, commitsParciais } : null,
|
|
384
|
+
reservas: pontaReservas ? { ref: pedido.reservas.branch, ...pontaReservas, arquivos: arquivos(dirReservas, mapaReservas),
|
|
385
|
+
parcial: parcial('reservasDir', 'reservas') } : null,
|
|
386
|
+
fabrica: pontaFabrica ? { ref: pedido.fabrica.branch, ...pontaFabrica, arquivos: arquivos(dirFabrica, mapaFabrica),
|
|
387
|
+
parcial: parcial('fabricaDir', 'fabrica') } : null } };
|
|
388
|
+
}
|
|
389
|
+
const CONSULTA_DE_COMMITS_GITHUB = `query($owner: String!, $name: String!, $oid: GitObjectID!, $desde: GitTimestamp!) {
|
|
390
|
+
repository(owner: $owner, name: $name) { object(oid: $oid) { ... on Commit {
|
|
391
|
+
history(since: $desde, first: 100) { pageInfo { hasNextPage } nodes { oid messageHeadline committedDate } } } } }
|
|
392
|
+
}`;
|
|
393
|
+
/**
|
|
394
|
+
* So os commits de um commit da base desde `desde`, quando a janela da primeira leitura ficou curta:
|
|
395
|
+
* o fuso do projeto so se conhece depois de ler o manifesto dele (achado 5 da rodada 2). So consulta.
|
|
396
|
+
*/
|
|
397
|
+
function lerCommitsDaForja(forja, pedido, executor = exports.executorPadrao) {
|
|
398
|
+
if (!SHA.test(pedido.commit))
|
|
399
|
+
return erro('forja.resposta-invalida', 'commit da base fora do formato');
|
|
400
|
+
try {
|
|
401
|
+
if (forja.tipo === 'gitlab')
|
|
402
|
+
return commitsDoGitlab(forja, pedido.commit, pedido.desde, executor);
|
|
403
|
+
const [owner, name] = forja.repo.split('/');
|
|
404
|
+
const r = chamarJson(executor, 'gh', ['api', 'graphql', '--method', 'POST', '--input', '-', ...hostnameDe(forja)], JSON.stringify({ query: CONSULTA_DE_COMMITS_GITHUB, variables: { owner, name, oid: pedido.commit, desde: pedido.desde } }));
|
|
405
|
+
if (!r.ok)
|
|
406
|
+
return r;
|
|
407
|
+
const dados = obj(r.json) ? r.json.data : undefined;
|
|
408
|
+
exigir(obj(dados) && obj(dados.repository), 'repositorio');
|
|
409
|
+
const objeto = dados.repository.object;
|
|
410
|
+
exigir(obj(objeto), 'commit');
|
|
411
|
+
return { ok: true, ...commitsDoHistorico(objeto.history, 'commits') };
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
if (e instanceof RespostaInvalida)
|
|
415
|
+
return erro('forja.resposta-invalida', `resposta fora do formato esperado em ${e.message}`);
|
|
416
|
+
throw e;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Le da forja a base (com `docs/roadmap`, o manifesto e os commits desde `desde`) e as duas branches
|
|
421
|
+
* de estado. So consulta; qualquer falha volta como erro tipado, com o detalhe redigido.
|
|
422
|
+
*/
|
|
423
|
+
function lerDaForja(forja, pedido, executor = exports.executorPadrao) {
|
|
424
|
+
const lidoEm = pedido.lidoEm ?? new Date().toISOString();
|
|
425
|
+
try {
|
|
426
|
+
return forja.tipo === 'github' ? lerDoGithub(forja, pedido, executor, lidoEm) : lerDoGitlab(forja, pedido, executor, lidoEm);
|
|
427
|
+
}
|
|
428
|
+
catch (e) {
|
|
429
|
+
if (e instanceof NaoEncontrado)
|
|
430
|
+
return erro('forja.nao-encontrado', `${(0, exports.rotuloDaForja)(forja)} não existe ou não é visível para o login da forja`);
|
|
431
|
+
if (e instanceof RespostaInvalida)
|
|
432
|
+
return erro('forja.resposta-invalida', `resposta fora do formato esperado em ${e.message}`);
|
|
433
|
+
throw e;
|
|
434
|
+
}
|
|
435
|
+
}
|
package/dist/gates.js
CHANGED
|
@@ -20,6 +20,7 @@ exports.registrarGateLiberado = registrarGateLiberado;
|
|
|
20
20
|
exports.descreverMotivo = descreverMotivo;
|
|
21
21
|
exports.aprovarGateHumano = aprovarGateHumano;
|
|
22
22
|
exports.aprovacaoHumanaProvada = aprovacaoHumanaProvada;
|
|
23
|
+
exports.reciboHumanoConfere = reciboHumanoConfere;
|
|
23
24
|
exports.aprovacoesHumanas = aprovacoesHumanas;
|
|
24
25
|
const ledger_1 = require("./ledger");
|
|
25
26
|
const modos_1 = require("./modos");
|
|
@@ -129,7 +130,14 @@ function formaDeAprovacao(e) {
|
|
|
129
130
|
* trocados no disco ou com canal trocado no ledger.
|
|
130
131
|
*/
|
|
131
132
|
function aprovacaoHumanaProvada(raiz, threadId, e) {
|
|
132
|
-
|
|
133
|
+
return formaDeAprovacao(e) && reciboHumanoConfere(raiz, threadId, e);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* K3.1: a mesma prova, para qualquer veredito do gate. O dossiê de decisão mostra a resposta do
|
|
137
|
+
* dono só quando o recibo do ingresso confere; aprovar ou recusar não muda o que se reconfere.
|
|
138
|
+
*/
|
|
139
|
+
function reciboHumanoConfere(raiz, threadId, e) {
|
|
140
|
+
if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.pausaHumana)
|
|
133
141
|
return false;
|
|
134
142
|
if (e.origem === 'telegram' || e.origem === 'native')
|
|
135
143
|
return (0, hitl_ingress_receipt_1.validarEvidenciaDoIngresso)(raiz, threadId, e);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CLASSES_QUE_NAO_BLOQUEIAM = exports.JANELA_PADRAO_MIN = void 0;
|
|
3
|
+
exports.MOTIVOS_DO_ORQUESTRADOR = exports.CLASSES_QUE_NAO_BLOQUEIAM = exports.JANELA_PADRAO_MIN = void 0;
|
|
4
4
|
exports.atoIrreversivelDoItem = atoIrreversivelDoItem;
|
|
5
|
+
exports.quemDecide = quemDecide;
|
|
5
6
|
exports.classificarItem = classificarItem;
|
|
6
7
|
exports.contarClassificacoes = contarClassificacoes;
|
|
7
8
|
/**
|
|
@@ -65,6 +66,15 @@ function atoIrreversivelDoItem(item) {
|
|
|
65
66
|
}
|
|
66
67
|
return hitl_contract_1.ATO_IRREVERSIVEL_DO_MOTIVO[item.motivo];
|
|
67
68
|
}
|
|
69
|
+
exports.MOTIVOS_DO_ORQUESTRADOR = Object.freeze([
|
|
70
|
+
'runtime.unavailable', 'runtime.silencio', 'runtime.rate-limited', 'artifact.missing',
|
|
71
|
+
'verify.failed', 'verify.regression', 'verify.timeout', 'verify.sem-veredito',
|
|
72
|
+
'claims.failed', 'claims.unverifiable', 'ci.failed', 'hitl.formato',
|
|
73
|
+
'lease.busy', 'vaga.stale', 'conducao.em-andamento', 'concurrency.limite',
|
|
74
|
+
]);
|
|
75
|
+
function quemDecide(motivo) {
|
|
76
|
+
return exports.MOTIVOS_DO_ORQUESTRADOR.includes(String(motivo ?? '')) ? 'orquestrador' : 'dono';
|
|
77
|
+
}
|
|
68
78
|
/** Nome curto da sessao, do jeito que o dono ja ve em `ork board` e nos comandos de log. */
|
|
69
79
|
const sessaoCurta = (id) => id.slice(0, 8);
|
|
70
80
|
function classificarItem(item, opcoes) {
|
package/dist/hitl-contract.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CONTRATOS_DE_PEDIDO = exports.CAMPOS_DE_GATE = exports.TETOS_HITL_V2 = exports.LETRAS_DE_ALTERNATIVA = exports.CONTRATO_HITL_V2 = exports.ATO_IRREVERSIVEL_DO_MOTIVO = exports.ATOS_IRREVERSIVEIS = exports.CONTRATO_HITL = void 0;
|
|
3
|
+
exports.CONTRATOS_DE_PEDIDO = exports.CAMPOS_DA_DECISAO_NO_MCP = exports.CAMPOS_DA_DECISAO_NO_CLI = exports.CAMPOS_DE_GATE = exports.TETOS_HITL_V2 = exports.LETRAS_DE_ALTERNATIVA = exports.CONTRATO_HITL_V2 = exports.ATO_IRREVERSIVEL_DO_MOTIVO = exports.ATOS_IRREVERSIVEIS = exports.CONTRATO_HITL = void 0;
|
|
4
4
|
exports.atoDaPausa = atoDaPausa;
|
|
5
5
|
exports.profundidadeDoModo = profundidadeDoModo;
|
|
6
6
|
exports.profundidadeDoPedido = profundidadeDoPedido;
|
|
@@ -9,6 +9,7 @@ exports.estadoDoPedido = estadoDoPedido;
|
|
|
9
9
|
exports.respostaAceitaDoPedido = respostaAceitaDoPedido;
|
|
10
10
|
exports.validarRespostaHitl = validarRespostaHitl;
|
|
11
11
|
exports.vereditoDoGate = vereditoDoGate;
|
|
12
|
+
exports.recusaNaSuperficie = recusaNaSuperficie;
|
|
12
13
|
exports.validarPedidoHitlV2 = validarPedidoHitlV2;
|
|
13
14
|
exports.validarPedidoHitl = validarPedidoHitl;
|
|
14
15
|
exports.ehV2 = ehV2;
|
|
@@ -216,24 +217,66 @@ function validarComumV2(v) {
|
|
|
216
217
|
throw new Error('pedido HITL v2: identificação, modo, instante ou profundidade inválidos');
|
|
217
218
|
}
|
|
218
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* RM-037 (rm037defeito, defeito 4): o que esta errado num campo de linha unica, ou null. A recusa diz
|
|
222
|
+
* o campo e o motivo real: texto presente e acima do teto respondia "exige o que foi decidido", e quem
|
|
223
|
+
* mandou os tres campos nao tinha como saber que o problema era o tamanho.
|
|
224
|
+
*/
|
|
225
|
+
function problemaDaLinha(campo, v, teto) {
|
|
226
|
+
if (typeof v !== 'string' || !v.trim())
|
|
227
|
+
return { ausente: true, texto: `falta ${campo}` };
|
|
228
|
+
if (/[\r\n]/.test(v))
|
|
229
|
+
return { ausente: false, texto: `${campo} tem quebra de linha; cada campo é uma linha só` };
|
|
230
|
+
if (/[\x00-\x08\x0b-\x1f\x7f]/.test(v))
|
|
231
|
+
return { ausente: false, texto: `${campo} tem caractere de controle` };
|
|
232
|
+
if (v.length > teto)
|
|
233
|
+
return { ausente: false, texto: `${campo} tem ${v.length} caracteres; o teto é ${teto}` };
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Os campos da decisao informada como cada superficie os mostra (achado S4 do CHECK): a recusa do contrato
|
|
238
|
+
* usa o nome interno, e quem digitou `--como-mudar` ou mandou `custoAgora` precisa ler o nome que usou.
|
|
239
|
+
*/
|
|
240
|
+
exports.CAMPOS_DA_DECISAO_NO_CLI = Object.freeze({
|
|
241
|
+
decidido: '--decidido', porque: '--porque', comoMudar: '--como-mudar', 'custoDeReverter.agora': '--custo-agora',
|
|
242
|
+
'custoDeReverter.depois': '--custo-depois', 'criterio.referencia': '--criterio',
|
|
243
|
+
});
|
|
244
|
+
exports.CAMPOS_DA_DECISAO_NO_MCP = Object.freeze({
|
|
245
|
+
decidido: 'decidido', porque: 'porque', comoMudar: 'comoMudar', 'custoDeReverter.agora': 'custoAgora',
|
|
246
|
+
'custoDeReverter.depois': 'custoDepois', 'criterio.referencia': 'criterio.referencia',
|
|
247
|
+
});
|
|
248
|
+
/** Troca o nome interno do campo, onde a recusa o cita, pelo nome da superficie. */
|
|
249
|
+
function recusaNaSuperficie(mensagem, nomes) {
|
|
250
|
+
return mensagem.replace(/(falta |: )(decidido|porque|comoMudar|custoDeReverter\.agora|custoDeReverter\.depois|criterio\.referencia)(?=\)| tem )/g, (_, antes, campo) => `${antes}${nomes[campo] ?? campo}`);
|
|
251
|
+
}
|
|
219
252
|
function validarDecidido(v) {
|
|
220
253
|
for (const campo of exports.CAMPOS_DE_GATE) {
|
|
221
254
|
if (v[campo] !== undefined)
|
|
222
255
|
throw new Error(`pedido HITL v2: decisão informada não pode trazer ${campo}`);
|
|
223
256
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
257
|
+
for (const campo of ['decidido', 'porque', 'comoMudar']) {
|
|
258
|
+
const problema = problemaDaLinha(campo, v[campo], exports.TETOS_HITL_V2.campoDaDecisao);
|
|
259
|
+
if (problema?.ausente)
|
|
260
|
+
throw new Error(`pedido HITL v2: decisão informada exige o que foi decidido, o porquê e como mudar (${problema.texto})`);
|
|
261
|
+
if (problema)
|
|
262
|
+
throw new Error(`pedido HITL v2: decisão informada: ${problema.texto}`);
|
|
227
263
|
}
|
|
228
264
|
const custo = v.custoDeReverter;
|
|
229
|
-
|
|
265
|
+
const problemasDoCusto = ['agora', 'depois']
|
|
266
|
+
.map(lado => problemaDaLinha(`custoDeReverter.${lado}`, objeto(custo) ? custo[lado] : undefined, exports.TETOS_HITL_V2.custo));
|
|
267
|
+
if (problemasDoCusto.some(p => p?.ausente)) {
|
|
230
268
|
throw new Error('pedido HITL v2: custo de reverter exige agora e depois, os dois presentes');
|
|
231
269
|
}
|
|
270
|
+
const doCusto = problemasDoCusto.find(p => p !== null);
|
|
271
|
+
if (doCusto)
|
|
272
|
+
throw new Error(`pedido HITL v2: custo de reverter: ${doCusto.texto}`);
|
|
232
273
|
const criterio = v.criterio;
|
|
233
|
-
|
|
234
|
-
|
|
274
|
+
const daReferencia = objeto(criterio) ? problemaDaLinha('criterio.referencia', criterio.referencia, 300) : null;
|
|
275
|
+
if (!objeto(criterio) || !['manifesto', 'ledger', 'medicao'].includes(criterio.tipo) || daReferencia?.ausente) {
|
|
235
276
|
throw new Error('pedido HITL v2: decisão informada exige critério citado e resolvível');
|
|
236
277
|
}
|
|
278
|
+
if (daReferencia)
|
|
279
|
+
throw new Error(`pedido HITL v2: critério: ${daReferencia.texto}`);
|
|
237
280
|
// R1, porta fechada: ato irreversivel nunca se qualifica como decisao informada.
|
|
238
281
|
if (v.irreversivel === true)
|
|
239
282
|
throw new Error('pedido HITL v2: ato irreversível nunca é decisão informada');
|