@orkastery/cli 0.4.2 → 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 -3
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +92 -39
- package/adapters/openclaw/openclaw.plugin.json +10 -3
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +32 -3
- package/adapters/openclaw/src/index.ts +108 -41
- 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 +88 -9
- package/dist/adapters/codex-events.js +3 -1
- 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 +117 -31
- 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/fix.js +1 -1
- package/dist/forja.js +435 -0
- package/dist/gates.js +10 -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 +605 -41
- 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 +36 -10
- 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/ratelimit.js +3 -2
- package/dist/retry.js +230 -9
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +213 -0
- package/dist/runtime-ambiente.js +12 -1
- package/dist/session-watcher-claude.js +26 -0
- package/dist/sessoes.js +49 -15
- package/dist/thread-close.js +3 -0
- package/dist/verify.js +5 -4
- package/dist/worktree.js +77 -0
- 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/roadmap-reservas.js
CHANGED
|
@@ -33,14 +33,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.REMOTO_PADRAO = exports.BRANCH_DE_RESERVAS = exports.CONTRATO_RESERVA = void 0;
|
|
36
|
+
exports.DIR_DE_FEATS = exports.CONTRATO_FEAT = exports.PRAZO_DO_FECHAMENTO_MS = exports.DIR_DE_RESERVAS = exports.REMOTO_PADRAO = exports.BRANCH_DE_RESERVAS = exports.CONTRATO_RESERVA = void 0;
|
|
37
|
+
exports.redeDoFechamento = redeDoFechamento;
|
|
37
38
|
exports.exigirItem = exigirItem;
|
|
38
39
|
exports.quemSouEu = quemSouEu;
|
|
40
|
+
exports.reservaValida = reservaValida;
|
|
39
41
|
exports.reservasLocais = reservasLocais;
|
|
40
42
|
exports.listarReservas = listarReservas;
|
|
43
|
+
exports.maiorFeatConhecida = maiorFeatConhecida;
|
|
44
|
+
exports.reservarFeat = reservarFeat;
|
|
41
45
|
exports.painelEmMarkdown = painelEmMarkdown;
|
|
42
46
|
exports.pegarItem = pegarItem;
|
|
43
47
|
exports.soltarItem = soltarItem;
|
|
48
|
+
exports.reservasOrfas = reservasOrfas;
|
|
49
|
+
exports.soltarReservaDaThread = soltarReservaDaThread;
|
|
50
|
+
exports.soltarReservasOrfas = soltarReservasOrfas;
|
|
44
51
|
exports.textoDasReservas = textoDasReservas;
|
|
45
52
|
/**
|
|
46
53
|
* I-47: reservas de item do roadmap entre maquinas, como documentacao em codigo.
|
|
@@ -64,15 +71,32 @@ const os = __importStar(require("node:os"));
|
|
|
64
71
|
const path = __importStar(require("node:path"));
|
|
65
72
|
const branch_de_estado_1 = require("./branch-de-estado");
|
|
66
73
|
const horario_1 = require("./horario");
|
|
74
|
+
const ledger_1 = require("./ledger");
|
|
67
75
|
const maquina_1 = require("./maquina");
|
|
76
|
+
const thread_1 = require("./thread");
|
|
68
77
|
const util_1 = require("./util");
|
|
69
78
|
exports.CONTRATO_RESERVA = 'ork.roadmap-reserva/v1';
|
|
70
79
|
exports.BRANCH_DE_RESERVAS = 'ork/roadmap-reservas';
|
|
71
80
|
exports.REMOTO_PADRAO = 'origin';
|
|
72
|
-
|
|
81
|
+
/** O diretorio das reservas na branch; a leitura sem clone (RM-054) le o mesmo. */
|
|
82
|
+
exports.DIR_DE_RESERVAS = 'reservas';
|
|
83
|
+
const DIR = exports.DIR_DE_RESERVAS;
|
|
73
84
|
const PAINEL = 'RESERVAS.md';
|
|
74
85
|
const TENTATIVAS = 5;
|
|
75
86
|
const ITEM = /^RM-\d{3}$/;
|
|
87
|
+
/**
|
|
88
|
+
* O fechamento nao pode ficar parado na rede (achado A3 do CHECK 1): prazo curto por chamada e git
|
|
89
|
+
* sem pergunta no terminal. O ssh de quem ja o configurou vale (aviso N1 do CHECK 2): com
|
|
90
|
+
* `GIT_SSH_COMMAND`, `GIT_SSH` ou `core.sshCommand`, nada muda nele e o prazo segura a espera; sem
|
|
91
|
+
* nenhum, o ssh roda em modo lote, que usa o agente e as chaves mas nunca pergunta a senha.
|
|
92
|
+
*/
|
|
93
|
+
exports.PRAZO_DO_FECHAMENTO_MS = 15000;
|
|
94
|
+
function redeDoFechamento(raiz) {
|
|
95
|
+
const sshProprio = !!process.env.GIT_SSH_COMMAND || !!process.env.GIT_SSH ||
|
|
96
|
+
(0, branch_de_estado_1.git)(raiz, ['config', '--get', 'core.sshCommand']).ok;
|
|
97
|
+
return { timeoutMs: exports.PRAZO_DO_FECHAMENTO_MS,
|
|
98
|
+
env: { GIT_TERMINAL_PROMPT: '0', ...(sshProprio ? {} : { GIT_SSH_COMMAND: 'ssh -o BatchMode=yes' }) } };
|
|
99
|
+
}
|
|
76
100
|
const PREFIXO = 'roadmap';
|
|
77
101
|
/** O item precisa existir no roadmap do projeto (`docs/roadmap/RM-NNN-*.md`). */
|
|
78
102
|
function exigirItem(raiz, item) {
|
|
@@ -95,14 +119,20 @@ function quemSouEu(raiz, opcoes = {}) {
|
|
|
95
119
|
return { por, maquina: (0, maquina_1.nomeDaMaquina)(opcoes.maquina) };
|
|
96
120
|
}
|
|
97
121
|
/** Traz a ponta da branch de reservas. `null` quando ela ainda nao existe no remoto. */
|
|
98
|
-
function buscar(raiz, remoto) {
|
|
99
|
-
return (0, branch_de_estado_1.buscarBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, PREFIXO);
|
|
122
|
+
function buscar(raiz, remoto, rede = {}) {
|
|
123
|
+
return (0, branch_de_estado_1.buscarBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, PREFIXO, rede.timeoutMs, rede.env);
|
|
100
124
|
}
|
|
101
125
|
function lerDaPonta(raiz, ponta) {
|
|
102
126
|
return (0, branch_de_estado_1.jsonsDaPonta)(raiz, ponta, DIR, PREFIXO)
|
|
103
127
|
.filter((r) => r.contrato === exports.CONTRATO_RESERVA)
|
|
104
128
|
.sort((a, b) => a.item.localeCompare(b.item));
|
|
105
129
|
}
|
|
130
|
+
/** RM-054: a reserva lida de fora do `ork` desta maquina (forja, outra copia) so vale inteira. */
|
|
131
|
+
function reservaValida(bruto) {
|
|
132
|
+
const r = bruto;
|
|
133
|
+
return !!r && typeof r === 'object' && r.contrato === exports.CONTRATO_RESERVA && typeof r.item === 'string' && ITEM.test(r.item) &&
|
|
134
|
+
typeof r.por === 'string' && typeof r.maquina === 'string' && typeof r.desdeEm === 'string';
|
|
135
|
+
}
|
|
106
136
|
/** As reservas da ultima copia lida nesta maquina, sem rede (I-51: o retrato da fabrica usa). */
|
|
107
137
|
function reservasLocais(raiz, remoto = exports.REMOTO_PADRAO) {
|
|
108
138
|
try {
|
|
@@ -114,11 +144,73 @@ function reservasLocais(raiz, remoto = exports.REMOTO_PADRAO) {
|
|
|
114
144
|
}
|
|
115
145
|
/** `ork roadmap reservas`: quem esta com cada item, lido do remoto (ou da ultima copia, sem rede). */
|
|
116
146
|
function listarReservas(raiz, opcoes = {}) {
|
|
117
|
-
const { ponta, atualizado } = buscar(raiz, opcoes.remoto ?? exports.REMOTO_PADRAO);
|
|
118
|
-
return { reservas: lerDaPonta(raiz, ponta), atualizado, ponta };
|
|
147
|
+
const { ponta, atualizado } = buscar(raiz, opcoes.remoto ?? exports.REMOTO_PADRAO, opcoes.rede);
|
|
148
|
+
return { reservas: lerDaPonta(raiz, ponta), feats: featsDaPonta(raiz, ponta), atualizado, ponta };
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// RM-037 (rm037noite, defeito 6): o numero da FEAT nova tambem e reservado.
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* A RM-052, a RM-026 e a RM-051 criaram a FEAT-030 cada uma, em maquinas e threads diferentes: o numero
|
|
155
|
+
* saia do `ls docs/produto` de cada branch. Agora ele sai de `ork roadmap feat`, na mesma branch de
|
|
156
|
+
* reservas e pela mesma atomicidade do item (o primeiro push vence; o segundo rele e leva o seguinte).
|
|
157
|
+
* A reserva de numero e para sempre: numero queimado nao volta, e por isso nunca colide.
|
|
158
|
+
*/
|
|
159
|
+
exports.CONTRATO_FEAT = 'ork.feat-reserva/v1';
|
|
160
|
+
exports.DIR_DE_FEATS = 'feats';
|
|
161
|
+
const NUMERO_DE_FEAT = /^FEAT-(\d{3})$/;
|
|
162
|
+
const ARQUIVO_DE_FEAT = /^FEAT-(\d{3})-/;
|
|
163
|
+
function featsDaPonta(raiz, ponta) {
|
|
164
|
+
return (0, branch_de_estado_1.jsonsDaPonta)(raiz, ponta, exports.DIR_DE_FEATS, PREFIXO)
|
|
165
|
+
.filter((r) => r && r.contrato === exports.CONTRATO_FEAT && typeof r.feat === 'string' && NUMERO_DE_FEAT.test(r.feat))
|
|
166
|
+
.sort((a, b) => a.feat.localeCompare(b.feat));
|
|
167
|
+
}
|
|
168
|
+
const numeroDaFeat = (feat) => Number(NUMERO_DE_FEAT.exec(feat)?.[1] ?? 0);
|
|
169
|
+
/**
|
|
170
|
+
* O maior numero de FEAT que o projeto ja conhece: a arvore de trabalho e a ponta de toda branch local
|
|
171
|
+
* e de toda branch do remoto ja buscada (achado A6 do CHECK 1: a FEAT criada a mao numa PR aberta, ou
|
|
172
|
+
* por um `ork` antigo, tambem conta). Branch que esta maquina nunca buscou continua invisivel.
|
|
173
|
+
*/
|
|
174
|
+
function maiorFeatConhecida(raiz, remoto = exports.REMOTO_PADRAO) {
|
|
175
|
+
const nomes = [];
|
|
176
|
+
const dir = path.join(raiz, 'docs', 'produto');
|
|
177
|
+
if (fs.existsSync(dir))
|
|
178
|
+
nomes.push(...fs.readdirSync(dir));
|
|
179
|
+
const refs = (0, branch_de_estado_1.git)(raiz, ['for-each-ref', '--format=%(objectname)', 'refs/heads', `refs/remotes/${remoto}`]);
|
|
180
|
+
const pontas = new Set(refs.ok ? refs.stdout.split('\n').map((l) => l.trim()).filter(Boolean) : []);
|
|
181
|
+
for (const ponta of pontas) {
|
|
182
|
+
const r = (0, branch_de_estado_1.git)(raiz, ['ls-tree', '--name-only', ponta, '--', 'docs/produto/']);
|
|
183
|
+
if (r.ok)
|
|
184
|
+
nomes.push(...r.stdout.split('\n').map((n) => path.posix.basename(n)));
|
|
185
|
+
}
|
|
186
|
+
return nomes.reduce((maior, nome) => Math.max(maior, Number(ARQUIVO_DE_FEAT.exec(nome)?.[1] ?? 0)), 0);
|
|
187
|
+
}
|
|
188
|
+
/** `ork roadmap feat`: reserva o proximo numero de FEAT para esta maquina, por push atomico. */
|
|
189
|
+
function reservarFeat(raiz, opcoes = {}) {
|
|
190
|
+
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
191
|
+
const eu = quemSouEu(raiz, opcoes);
|
|
192
|
+
for (let tentativa = 1; tentativa <= TENTATIVAS; tentativa++) {
|
|
193
|
+
const { ponta, atualizado } = buscar(raiz, remoto);
|
|
194
|
+
if (!atualizado)
|
|
195
|
+
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS} em ${remoto}; reservar exige rede`);
|
|
196
|
+
const feats = featsDaPonta(raiz, ponta);
|
|
197
|
+
const proximo = Math.max(maiorFeatConhecida(raiz, remoto), ...feats.map((r) => numeroDaFeat(r.feat))) + 1;
|
|
198
|
+
if (proximo > 999)
|
|
199
|
+
throw new Error('roadmap.feat: os numeros de FEAT de tres digitos acabaram');
|
|
200
|
+
const feat = `FEAT-${String(proximo).padStart(3, '0')}`;
|
|
201
|
+
const reserva = { contrato: exports.CONTRATO_FEAT, feat, por: eu.por, maquina: eu.maquina,
|
|
202
|
+
thread: opcoes.thread ?? null, nota: opcoes.nota ?? null, em: opcoes.agora ?? (0, util_1.agora)() };
|
|
203
|
+
const commit = (0, branch_de_estado_1.gravarNaBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, ponta, [
|
|
204
|
+
{ caminho: `${exports.DIR_DE_FEATS}/${feat}.json`, conteudo: JSON.stringify(reserva, null, 2) + '\n' },
|
|
205
|
+
{ caminho: PAINEL, conteudo: painelEmMarkdown(lerDaPonta(raiz, ponta), [...feats, reserva]) },
|
|
206
|
+
], `reserva: ${feat} para ${eu.por} em ${eu.maquina}${reserva.thread ? ` (thread ${reserva.thread})` : ''}`, PREFIXO);
|
|
207
|
+
if (commit)
|
|
208
|
+
return { feat, reserva, commit, tentativas: tentativa };
|
|
209
|
+
}
|
|
210
|
+
throw new Error(`roadmap.concorrencia: ${TENTATIVAS} pushes recusados seguidos; tente de novo em instantes`);
|
|
119
211
|
}
|
|
120
212
|
/** O `RESERVAS.md` da branch: a mesma lista, para quem abre o GitHub. */
|
|
121
|
-
function painelEmMarkdown(reservas) {
|
|
213
|
+
function painelEmMarkdown(reservas, feats = []) {
|
|
122
214
|
const linhas = [
|
|
123
215
|
'# Reservas do roadmap',
|
|
124
216
|
'',
|
|
@@ -126,20 +218,24 @@ function painelEmMarkdown(reservas) {
|
|
|
126
218
|
'não edite à mão. Antes de começar um item: `ork roadmap reservas`.',
|
|
127
219
|
'',
|
|
128
220
|
];
|
|
221
|
+
const numeros = feats.length === 0 ? [] : ['', '## Números de FEAT reservados', '',
|
|
222
|
+
'O número da FEAT nova sai de `ork roadmap feat`; número reservado não volta.', '',
|
|
223
|
+
'| FEAT | Com quem | Máquina | Thread | Em |', '| --- | --- | --- | --- | --- |',
|
|
224
|
+
...feats.map((f) => `| ${f.feat} | ${f.por} | ${f.maquina} | ${f.thread ?? 'sem thread'} | ${(0, horario_1.formatarDataHora)(f.em)} |`)];
|
|
129
225
|
if (reservas.length === 0)
|
|
130
|
-
return [...linhas, 'Nenhum item reservado.', ''].join('\n');
|
|
226
|
+
return [...linhas, 'Nenhum item reservado.', ...numeros, ''].join('\n');
|
|
131
227
|
linhas.push('| Item | Com quem | Máquina | Thread | Desde | Nota |', '| --- | --- | --- | --- | --- | --- |');
|
|
132
228
|
for (const r of reservas) {
|
|
133
229
|
linhas.push(`| ${r.item} | ${r.por} | ${r.maquina} | ${r.thread ?? '—'} | ${(0, horario_1.formatarDataHora)(r.desdeEm)} | ${r.nota ?? '—'} |`);
|
|
134
230
|
}
|
|
135
|
-
return [...linhas, '', (0, horario_1.legendaDoFuso)(), ''].join('\n');
|
|
231
|
+
return [...linhas, ...numeros, '', (0, horario_1.legendaDoFuso)(), ''].join('\n');
|
|
136
232
|
}
|
|
137
233
|
/** Grava a reserva (ou a remocao dela) e o painel numa versao nova da branch. Recusa volta `false`. */
|
|
138
|
-
function gravar(raiz, remoto, ponta, mudanca, todas, mensagem) {
|
|
234
|
+
function gravar(raiz, remoto, ponta, mudanca, todas, mensagem, rede = {}) {
|
|
139
235
|
return (0, branch_de_estado_1.gravarNaBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, ponta, [
|
|
140
236
|
{ caminho: `${DIR}/${mudanca.item}.json`, conteudo: mudanca.reserva ? JSON.stringify(mudanca.reserva, null, 2) + '\n' : null },
|
|
141
|
-
{ caminho: PAINEL, conteudo: painelEmMarkdown(todas) },
|
|
142
|
-
], mensagem, PREFIXO);
|
|
237
|
+
{ caminho: PAINEL, conteudo: painelEmMarkdown(todas, featsDaPonta(raiz, ponta)) },
|
|
238
|
+
], mensagem, PREFIXO, rede);
|
|
143
239
|
}
|
|
144
240
|
function ehMinha(r, eu) {
|
|
145
241
|
return r.por === eu.por && r.maquina === eu.maquina;
|
|
@@ -163,11 +259,14 @@ function pegarItem(raiz, item, opcoes = {}) {
|
|
|
163
259
|
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
164
260
|
const eu = quemSouEu(raiz, opcoes);
|
|
165
261
|
for (let tentativa = 1; tentativa <= TENTATIVAS; tentativa++) {
|
|
166
|
-
const { ponta, atualizado } = buscar(raiz, remoto);
|
|
262
|
+
const { ponta, atualizado } = buscar(raiz, remoto, opcoes.rede);
|
|
167
263
|
if (!atualizado)
|
|
168
264
|
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS} em ${remoto}; reservar exige rede`);
|
|
169
265
|
const todas = lerDaPonta(raiz, ponta);
|
|
170
266
|
const atual = todas.find((r) => r.item === id) ?? null;
|
|
267
|
+
if (opcoes.threadEsperada !== undefined && (atual?.thread ?? null) !== opcoes.threadEsperada) {
|
|
268
|
+
return { acao: 'nada', item: id, reserva: atual, commit: null, tentativas: tentativa };
|
|
269
|
+
}
|
|
171
270
|
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
172
271
|
let acao = 'pegou';
|
|
173
272
|
let tomadaDe;
|
|
@@ -191,7 +290,7 @@ function pegarItem(raiz, item, opcoes = {}) {
|
|
|
191
290
|
const mensagem = acao === 'tomou'
|
|
192
291
|
? `reserva: ${id} tomada por ${eu.por} em ${eu.maquina} (de ${tomadaDe.por} em ${tomadaDe.maquina}): ${tomadaDe.motivo}`
|
|
193
292
|
: `reserva: ${id} ${acao === 'renovou' ? 'renovada' : 'pega'} por ${eu.por} em ${eu.maquina}`;
|
|
194
|
-
const commit = gravar(raiz, remoto, ponta, { item: id, reserva }, novas, mensagem);
|
|
293
|
+
const commit = gravar(raiz, remoto, ponta, { item: id, reserva }, novas, mensagem, opcoes.rede);
|
|
195
294
|
if (commit)
|
|
196
295
|
return { acao, item: id, reserva, commit, tentativas: tentativa };
|
|
197
296
|
}
|
|
@@ -203,13 +302,16 @@ function soltarItem(raiz, item, opcoes = {}) {
|
|
|
203
302
|
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
204
303
|
const eu = quemSouEu(raiz, opcoes);
|
|
205
304
|
for (let tentativa = 1; tentativa <= TENTATIVAS; tentativa++) {
|
|
206
|
-
const { ponta, atualizado } = buscar(raiz, remoto);
|
|
305
|
+
const { ponta, atualizado } = buscar(raiz, remoto, opcoes.rede);
|
|
207
306
|
if (!atualizado)
|
|
208
307
|
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS} em ${remoto}; soltar exige rede`);
|
|
209
308
|
const todas = lerDaPonta(raiz, ponta);
|
|
210
309
|
const atual = todas.find((r) => r.item === id);
|
|
211
310
|
if (!atual)
|
|
212
311
|
return { acao: 'nada', item: id, reserva: null, commit: null, tentativas: tentativa };
|
|
312
|
+
if (opcoes.threadEsperada !== undefined && (atual.thread ?? null) !== opcoes.threadEsperada) {
|
|
313
|
+
return { acao: 'nada', item: id, reserva: atual, commit: null, tentativas: tentativa };
|
|
314
|
+
}
|
|
213
315
|
let motivo = '';
|
|
214
316
|
if (!ehMinha(atual, eu)) {
|
|
215
317
|
if (!opcoes.forcar)
|
|
@@ -218,26 +320,166 @@ function soltarItem(raiz, item, opcoes = {}) {
|
|
|
218
320
|
}
|
|
219
321
|
const restantes = todas.filter((r) => r.item !== id);
|
|
220
322
|
const mensagem = `reserva: ${id} solta por ${eu.por} em ${eu.maquina}${motivo}`;
|
|
221
|
-
const commit = gravar(raiz, remoto, ponta, { item: id, reserva: null }, restantes, mensagem);
|
|
323
|
+
const commit = gravar(raiz, remoto, ponta, { item: id, reserva: null }, restantes, mensagem, opcoes.rede);
|
|
222
324
|
if (commit)
|
|
223
325
|
return { acao: 'soltou', item: id, reserva: atual, commit, tentativas: tentativa };
|
|
224
326
|
}
|
|
225
327
|
throw new Error(`roadmap.concorrencia: ${TENTATIVAS} pushes recusados seguidos; tente de novo em instantes`);
|
|
226
328
|
}
|
|
329
|
+
function threadsLocais(raiz) {
|
|
330
|
+
const threads = [];
|
|
331
|
+
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
332
|
+
try {
|
|
333
|
+
threads.push((0, thread_1.lerThread)(raiz, id));
|
|
334
|
+
}
|
|
335
|
+
catch { /* thread ilegivel nao decide reserva */ }
|
|
336
|
+
}
|
|
337
|
+
return threads;
|
|
338
|
+
}
|
|
339
|
+
/** A outra thread aberta do item nesta maquina, a mais recente, que herda a reserva. */
|
|
340
|
+
function sucessoraDoItem(threads, item, fora) {
|
|
341
|
+
const abertas = threads.filter((t) => t.id !== fora && t.status !== 'fechada' && t.roadmap === item)
|
|
342
|
+
.sort((a, b) => String(b.atualizadaEm ?? '').localeCompare(String(a.atualizadaEm ?? '')));
|
|
343
|
+
return abertas[0]?.id ?? null;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* As reservas orfas: desta maquina, apontando para thread que existe aqui e esta fechada. Thread que
|
|
347
|
+
* nao existe aqui nao prova nada (pode ser de outra copia), e reserva de outra maquina so sai com
|
|
348
|
+
* `--forcar`, pelo contrato da I-47.
|
|
349
|
+
*/
|
|
350
|
+
function reservasOrfas(raiz, reservas, opcoes = {}) {
|
|
351
|
+
const eu = quemSouEu(raiz, opcoes);
|
|
352
|
+
const threads = threadsLocais(raiz);
|
|
353
|
+
const status = new Map(threads.map((t) => [t.id, t.status]));
|
|
354
|
+
return reservas.filter((r) => r.thread && ehMinha(r, eu) && status.get(r.thread) === 'fechada')
|
|
355
|
+
.map((reserva) => ({ reserva, sucessora: sucessoraDoItem(threads, reserva.item, reserva.thread) }));
|
|
356
|
+
}
|
|
357
|
+
/** Solta a reserva orfa, ou a reaponta para a sucessora, e registra no ledger da thread fechada. */
|
|
358
|
+
function soltarOrfa(raiz, orfa, origem, opcoes) {
|
|
359
|
+
const thread = orfa.reserva.thread, item = orfa.reserva.item;
|
|
360
|
+
const comparando = { ...opcoes, threadEsperada: thread };
|
|
361
|
+
const r = orfa.sucessora
|
|
362
|
+
? pegarItem(raiz, item, { ...comparando, thread: orfa.sucessora })
|
|
363
|
+
: soltarItem(raiz, item, comparando);
|
|
364
|
+
// A reserva mudou de dono no meio (ou ja tinha saido): nada foi gravado e nada vai ao ledger como soltura.
|
|
365
|
+
if (r.acao === 'nada') {
|
|
366
|
+
return { item, thread, acao: 'intocada', para: null, commit: null,
|
|
367
|
+
detalhe: `${item} mudou de dono entre a leitura e a gravacao; ficou como estava` };
|
|
368
|
+
}
|
|
369
|
+
const acao = orfa.sucessora ? 'reapontada' : 'solta';
|
|
370
|
+
const detalhe = orfa.sucessora ? `${item} passou para a thread ${orfa.sucessora}, aberta no mesmo item` : `${item} devolvido`;
|
|
371
|
+
// Sugestao 3 do CHECK 2: a soltura ja foi ao remoto; erro so no registro nao a transforma em pendencia.
|
|
372
|
+
try {
|
|
373
|
+
(0, ledger_1.registrarSeExiste)((0, thread_1.dirThread)(raiz, thread), thread, ledger_1.TIPOS_DE_EVENTO.reservaLiberada, { item, acao, para: orfa.sucessora, commit: r.commit, origem, detalhe });
|
|
374
|
+
}
|
|
375
|
+
catch { /* a reserva saiu; o resultado diz o que aconteceu */ }
|
|
376
|
+
return { item, thread, acao, para: orfa.sucessora, commit: r.commit, detalhe };
|
|
377
|
+
}
|
|
378
|
+
/** A pendencia vai ao ledger da thread fechada, com a correcao; o registro tambem e de melhor esforco. */
|
|
379
|
+
function pendenteDaThread(raiz, thread, item, detalhe) {
|
|
380
|
+
try {
|
|
381
|
+
(0, ledger_1.registrarSeExiste)((0, thread_1.dirThread)(raiz, thread), thread, ledger_1.TIPOS_DE_EVENTO.reservaPendente, { item, detalhe, correcao: 'ork roadmap reservas --soltar-orfas' });
|
|
382
|
+
}
|
|
383
|
+
catch { /* sem ledger gravavel, o resultado ainda diz a pendencia */ }
|
|
384
|
+
return { item: item ?? '', thread, acao: 'pendente', para: null, commit: null, detalhe };
|
|
385
|
+
}
|
|
386
|
+
const primeiraLinha = (e) => String(e?.message ?? e).split('\n')[0].slice(0, 300);
|
|
387
|
+
/**
|
|
388
|
+
* `ork thread close` e `ork master`: a thread que fecha solta a reserva do item dela, ou a passa para
|
|
389
|
+
* outra thread aberta do mesmo item nesta maquina. De melhor esforco: sem rede ou com a reserva em
|
|
390
|
+
* outra maquina, o fechamento segue e o ledger guarda a pendencia com a correcao. So vai a rede
|
|
391
|
+
* quando ha o que soltar: a thread tem item ou a ultima copia lida das reservas aponta para ela.
|
|
392
|
+
*/
|
|
393
|
+
function soltarReservaDaThread(raiz, threadId, opcoes = {}) {
|
|
394
|
+
let item = null;
|
|
395
|
+
try {
|
|
396
|
+
item = (0, thread_1.lerThread)(raiz, threadId).roadmap ?? null;
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
402
|
+
const naCopia = reservasLocais(raiz, remoto).find((r) => r.thread === threadId);
|
|
403
|
+
if (!item && !naCopia)
|
|
404
|
+
return [];
|
|
405
|
+
// Sugestao 1 do CHECK 2: quem passa `rede` troca o que passou, sem perder o resto do fechamento.
|
|
406
|
+
const base = redeDoFechamento(raiz);
|
|
407
|
+
const rede = { timeoutMs: opcoes.rede?.timeoutMs ?? base.timeoutMs, env: { ...base.env, ...opcoes.rede?.env } };
|
|
408
|
+
const comRede = { ...opcoes, rede };
|
|
409
|
+
const oItem = item ?? naCopia?.item ?? null;
|
|
410
|
+
let painel;
|
|
411
|
+
try {
|
|
412
|
+
painel = listarReservas(raiz, { remoto, rede });
|
|
413
|
+
}
|
|
414
|
+
catch (e) {
|
|
415
|
+
return [pendenteDaThread(raiz, threadId, oItem, primeiraLinha(e))];
|
|
416
|
+
}
|
|
417
|
+
if (!painel.atualizado) {
|
|
418
|
+
return [pendenteDaThread(raiz, threadId, oItem, `sem leitura de ${exports.BRANCH_DE_RESERVAS} em ${remoto}; a reserva fica para depois`)];
|
|
419
|
+
}
|
|
420
|
+
const dela = painel.reservas.filter((r) => r.thread === threadId);
|
|
421
|
+
let orfas;
|
|
422
|
+
// Sugestao 2 do CHECK 2: quem sou eu e as threads locais tambem podem falhar; vira pendencia, nao silencio.
|
|
423
|
+
try {
|
|
424
|
+
orfas = reservasOrfas(raiz, dela, opcoes);
|
|
425
|
+
}
|
|
426
|
+
catch (e) {
|
|
427
|
+
return dela.map((r) => pendenteDaThread(raiz, threadId, r.item, primeiraLinha(e)));
|
|
428
|
+
}
|
|
429
|
+
const saida = [];
|
|
430
|
+
// Sugestao 1 do CHECK 1: toda reserva da thread, nao so a primeira; a de outra maquina fica pendente.
|
|
431
|
+
for (const r of dela.filter((x) => !orfas.some((o) => o.reserva.item === x.item))) {
|
|
432
|
+
saida.push(pendenteDaThread(raiz, threadId, r.item, `${descrever(r)}; so quem pegou solta, ou --forcar --motivo`));
|
|
433
|
+
}
|
|
434
|
+
for (const orfa of orfas) {
|
|
435
|
+
try {
|
|
436
|
+
saida.push(soltarOrfa(raiz, orfa, 'fechamento', comRede));
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
saida.push(pendenteDaThread(raiz, threadId, orfa.reserva.item, primeiraLinha(e)));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return saida;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* `ork roadmap reservas --soltar-orfas`: a mesma regra do fechamento, para toda reserva orfa desta
|
|
446
|
+
* maquina. Uma orfa que falha vira pendencia no ledger dela e nao para as outras (sugestao 2 do CHECK 1).
|
|
447
|
+
*/
|
|
448
|
+
function soltarReservasOrfas(raiz, opcoes = {}) {
|
|
449
|
+
const painel = listarReservas(raiz, { remoto: opcoes.remoto, rede: opcoes.rede });
|
|
450
|
+
if (!painel.atualizado)
|
|
451
|
+
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS}; soltar exige rede`);
|
|
452
|
+
return reservasOrfas(raiz, painel.reservas, opcoes).map((orfa) => {
|
|
453
|
+
try {
|
|
454
|
+
return soltarOrfa(raiz, orfa, 'orfas', opcoes);
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
return pendenteDaThread(raiz, orfa.reserva.thread, orfa.reserva.item, primeiraLinha(e));
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
227
461
|
/** O texto de `ork roadmap reservas` para o terminal, no fuso do dono. */
|
|
228
|
-
function textoDasReservas(p) {
|
|
462
|
+
function textoDasReservas(p, orfas = []) {
|
|
229
463
|
const linhas = [];
|
|
230
464
|
if (!p.atualizado)
|
|
231
465
|
linhas.push('AVISO: sem acesso ao remoto; esta e a ultima copia lida nesta maquina.', '');
|
|
466
|
+
const feats = p.feats ?? [];
|
|
467
|
+
const numeros = feats.length === 0 ? [] : ['', `Números de FEAT reservados (${feats.length}), os últimos: ` +
|
|
468
|
+
feats.slice(-5).map((f) => `${f.feat} (${f.thread ?? f.maquina})`).join(', ') + '. O próximo sai de: ork roadmap feat'];
|
|
232
469
|
if (p.reservas.length === 0) {
|
|
233
|
-
linhas.push('Nenhum item do roadmap reservado.');
|
|
470
|
+
linhas.push('Nenhum item do roadmap reservado.', ...numeros);
|
|
234
471
|
return linhas.join('\n');
|
|
235
472
|
}
|
|
236
473
|
linhas.push('ITEM COM QUEM MAQUINA THREAD DESDE');
|
|
474
|
+
const orfaDoItem = new Map(orfas.map((o) => [o.reserva.item, o]));
|
|
237
475
|
for (const r of p.reservas) {
|
|
476
|
+
const orfa = orfaDoItem.get(r.item);
|
|
477
|
+
const marca = orfa ? ` ÓRFÃ: thread fechada${orfa.sucessora ? `, passa para ${orfa.sucessora}` : ''}` : '';
|
|
238
478
|
linhas.push(`${r.item.padEnd(8)} ${r.por.slice(0, 19).padEnd(19)} ${r.maquina.slice(0, 15).padEnd(15)} ` +
|
|
239
|
-
`${(r.thread ?? '-').slice(0, 18).padEnd(18)} ${(0, horario_1.formatarDataHora)(r.desdeEm)}` + (r.nota ? ` ${r.nota}` : ''));
|
|
479
|
+
`${(r.thread ?? '-').slice(0, 18).padEnd(18)} ${(0, horario_1.formatarDataHora)(r.desdeEm)}` + (r.nota ? ` ${r.nota}` : '') + marca);
|
|
240
480
|
}
|
|
241
|
-
|
|
481
|
+
if (orfas.length > 0)
|
|
482
|
+
linhas.push('', `${orfas.length} reserva(s) órfã(s) desta máquina. Para soltar: ork roadmap reservas --soltar-orfas`);
|
|
483
|
+
linhas.push(...numeros, '', (0, horario_1.legendaDoFuso)());
|
|
242
484
|
return linhas.join('\n');
|
|
243
485
|
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TETO_DO_FECHO = exports.GRUPO_DO_CICLO = exports.GRUPOS_DO_ROADMAP = exports.CONTRATO_STATUS_DO_ROADMAP = void 0;
|
|
4
|
+
exports.esperaDoDono = esperaDoDono;
|
|
5
|
+
exports.fatosLocais = fatosLocais;
|
|
6
|
+
exports.montarStatusDoRoadmap = montarStatusDoRoadmap;
|
|
7
|
+
exports.montarStatusDeFatos = montarStatusDeFatos;
|
|
8
|
+
exports.textoDoStatusDoRoadmap = textoDoStatusDoRoadmap;
|
|
9
|
+
/**
|
|
10
|
+
* RM-048 (item 7, D7): o status report UNICO do roadmap, no formato aprovado pelo dono.
|
|
11
|
+
*
|
|
12
|
+
* Ate aqui cada canal escrevia o proprio relatorio de roadmap, e o dono lia uma coisa no Telegram
|
|
13
|
+
* e outra no terminal. O formato que ele aprovou (27/09 e 28/09):
|
|
14
|
+
*
|
|
15
|
+
* titulo "Roadmap do <Projeto> (DD/MM, HH:MM)", no fuso do dono;
|
|
16
|
+
* grupos com icones, nesta ordem: Concluidos, Disponiveis com algo em aberto, Entregue hoje,
|
|
17
|
+
* Piloto, Em desenvolvimento, Refinamento, Proposto, Descontinuado;
|
|
18
|
+
* um item por linha, com #HITL no que depende do dono;
|
|
19
|
+
* fecho com "O que precisa de voce" e "O que eu faco em seguida".
|
|
20
|
+
*
|
|
21
|
+
* Tudo sai de FATO: o frontmatter do item (`ork docs`), o ledger das threads ligadas a ele e o gate
|
|
22
|
+
* que o nucleo confere sem escrever (`prepararPedidoGate`). E leitura pura: nao abre pedido, nao
|
|
23
|
+
* grava ledger, nao toca a rede. Os canais chamam `ork roadmap status` e transportam o texto.
|
|
24
|
+
*
|
|
25
|
+
* RM-054: o montador recebe FATOS de thread (`FatoDeThread`), nao so as threads deste disco. O
|
|
26
|
+
* relatorio local continua com os fatos locais; o panorama da rede soma os retratos das outras
|
|
27
|
+
* maquinas, e so ai o fecho diz entre parenteses em que maquina a thread anda.
|
|
28
|
+
*/
|
|
29
|
+
const docs_1 = require("./docs");
|
|
30
|
+
const ledger_1 = require("./ledger");
|
|
31
|
+
const thread_1 = require("./thread");
|
|
32
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
33
|
+
const hitl_gates_1 = require("./hitl-gates");
|
|
34
|
+
const hitl_classificacao_1 = require("./hitl-classificacao");
|
|
35
|
+
const horario_1 = require("./horario");
|
|
36
|
+
const projeto_alvo_1 = require("./projeto-alvo");
|
|
37
|
+
exports.CONTRATO_STATUS_DO_ROADMAP = 'ork.roadmap-status/v1';
|
|
38
|
+
/** Os grupos, na ordem e com os icones que o dono aprovou. */
|
|
39
|
+
exports.GRUPOS_DO_ROADMAP = Object.freeze([
|
|
40
|
+
{ id: 'concluidos', icone: '✅', titulo: 'Concluídos' },
|
|
41
|
+
{ id: 'disponiveis', icone: '🟢', titulo: 'Disponíveis com algo em aberto' },
|
|
42
|
+
{ id: 'hoje', icone: '🚀', titulo: 'Entregue hoje' },
|
|
43
|
+
{ id: 'piloto', icone: '🧪', titulo: 'Piloto' },
|
|
44
|
+
{ id: 'desenvolvimento', icone: '🔨', titulo: 'Em desenvolvimento' },
|
|
45
|
+
{ id: 'refinamento', icone: '🔍', titulo: 'Refinamento' },
|
|
46
|
+
{ id: 'proposto', icone: '🆕', titulo: 'Proposto' },
|
|
47
|
+
{ id: 'descontinuado', icone: '⛔', titulo: 'Descontinuado' },
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* D7: o grupo de cada ciclo do padrao de roadmap (`docs/padroes/roadmap-de-produto.md`). Mapa
|
|
51
|
+
* TOTAL sobre os doze ciclos; ciclo fora dele cai em "Proposto" e o texto o mostra como e.
|
|
52
|
+
*/
|
|
53
|
+
exports.GRUPO_DO_CICLO = Object.freeze({
|
|
54
|
+
'Concluído': 'concluidos',
|
|
55
|
+
'Disponível': 'disponiveis',
|
|
56
|
+
'Piloto': 'piloto',
|
|
57
|
+
'Em desenvolvimento': 'desenvolvimento', 'Em validação': 'desenvolvimento', 'Bloqueado': 'desenvolvimento',
|
|
58
|
+
'Refinamento': 'refinamento', 'Pronto para desenvolvimento': 'refinamento',
|
|
59
|
+
'Discovery': 'proposto', 'Backlog': 'proposto',
|
|
60
|
+
'Cancelado': 'descontinuado', 'Descontinuado': 'descontinuado',
|
|
61
|
+
});
|
|
62
|
+
/** Quantas linhas cada parte do fecho mostra antes de contar o resto. */
|
|
63
|
+
exports.TETO_DO_FECHO = 5;
|
|
64
|
+
/** A letra recomendada de uma pergunta v2, para o fecho dizer exatamente o que digitar. */
|
|
65
|
+
const recomendadaDe = (p) => (0, hitl_contract_1.ehV2)(p) && p.classe === 'pergunta' ? p.alternativas.find(a => a.recomendada)?.letra : undefined;
|
|
66
|
+
const mapa = (v) => (!!v && typeof v === 'object' && !Array.isArray(v) ? v : {});
|
|
67
|
+
const texto = (v) => (v === undefined || v === null ? '' : String(v));
|
|
68
|
+
/** O nome curto do item: o titulo ate o primeiro ":" ou 60 caracteres. */
|
|
69
|
+
function nomeCurto(titulo) {
|
|
70
|
+
const antes = titulo.split(/:\s/)[0].trim();
|
|
71
|
+
return antes.length > 60 ? `${antes.slice(0, 57)}...` : antes;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* O que espera o dono nesta thread, lido sem escrever: um gate que ainda espera (pausa do modo ou
|
|
75
|
+
* escalacao tipada do dono) ou uma pergunta de sessao aberta. Impedimento tecnico nao conta (D6).
|
|
76
|
+
*/
|
|
77
|
+
function esperaDoDono(raiz, t, quando) {
|
|
78
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id));
|
|
79
|
+
const escalacoes = [...new Set(eventos.filter(e => e.tipo === 'gate_blocked' && e.fase === t.faseAtual &&
|
|
80
|
+
hitl_gates_1.MOTIVOS_DE_ESCALACAO_HUMANA.includes(String(e.motivo)) && (0, hitl_classificacao_1.quemDecide)(String(e.motivo)) === 'dono')
|
|
81
|
+
.map(e => String(e.motivo)))];
|
|
82
|
+
for (const motivo of ['human.pending', ...escalacoes]) {
|
|
83
|
+
try {
|
|
84
|
+
const preparado = (0, hitl_gates_1.prepararPedidoGate)(raiz, t.id, motivo, quando);
|
|
85
|
+
const pedido = 'aberto' in preparado ? preparado.aberto : preparado.novo;
|
|
86
|
+
const codigo = 'aberto' in preparado ? pedido.codigo : undefined;
|
|
87
|
+
const recomendada = recomendadaDe(pedido);
|
|
88
|
+
return { thread: t.id, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido), ...(typeof codigo === 'string' ? { codigo } : {}),
|
|
89
|
+
...(recomendada ? { recomendada } : {}) };
|
|
90
|
+
}
|
|
91
|
+
catch { /* este motivo nao espera o dono agora */ }
|
|
92
|
+
}
|
|
93
|
+
const contexto = (0, hitl_gates_1.contextoHitlDosEventos)(t, eventos);
|
|
94
|
+
const respondidos = new Set(eventos.filter(e => ['human_gate', 'session_answered'].includes(e.tipo)).map(e => String(e.pedidoId)));
|
|
95
|
+
for (const e of [...eventos].reverse()) {
|
|
96
|
+
if (e.tipo !== 'hitl_requested' || e.contexto !== contexto)
|
|
97
|
+
continue;
|
|
98
|
+
const pedido = e.pedido;
|
|
99
|
+
try {
|
|
100
|
+
if ((0, hitl_contract_1.alvoDoPedido)(pedido)?.tipo !== 'session' || respondidos.has(pedido.id) || (0, hitl_contract_1.estadoDoPedido)(pedido, quando) !== 'aberto')
|
|
101
|
+
continue;
|
|
102
|
+
const codigo = (0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' ? pedido.codigo : undefined;
|
|
103
|
+
const recomendada = recomendadaDe(pedido);
|
|
104
|
+
return { thread: t.id, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido), ...(codigo ? { codigo } : {}), ...(recomendada ? { recomendada } : {}) };
|
|
105
|
+
}
|
|
106
|
+
catch { /* pedido invalido nao e espera */ }
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Os fatos das threads deste disco: `ship_done` do dia e espera do dono lidos do ledger local.
|
|
112
|
+
* `fuso`: o do projeto consultado, na visao da rede; sem ele, o do dono deste processo.
|
|
113
|
+
*/
|
|
114
|
+
function fatosLocais(raiz, quando, fuso) {
|
|
115
|
+
const hoje = (0, horario_1.dataLocal)(quando, fuso);
|
|
116
|
+
const fatos = [];
|
|
117
|
+
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
118
|
+
let t;
|
|
119
|
+
try {
|
|
120
|
+
t = (0, thread_1.lerThread)(raiz, id);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue; /* thread ilegivel fica de fora */
|
|
124
|
+
}
|
|
125
|
+
fatos.push({
|
|
126
|
+
id: t.id, roadmap: t.roadmap ?? null, aberta: t.status !== 'fechada', fase: t.faseAtual,
|
|
127
|
+
entregueHoje: () => (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id)).some(e => e.tipo === 'ship_done' && (0, horario_1.dataLocal)(e.ts, fuso) === hoje),
|
|
128
|
+
espera: () => esperaDoDono(raiz, t, quando),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return fatos;
|
|
132
|
+
}
|
|
133
|
+
/** Monta o relatorio. Leitura pura: nada e escrito, nenhum pedido e aberto. */
|
|
134
|
+
function montarStatusDoRoadmap(raiz, opcoes = {}) {
|
|
135
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
136
|
+
return montarStatusDeFatos((0, docs_1.carregarDocs)(raiz).docs, fatosLocais(raiz, quando), { quando, projeto: opcoes.projeto, consulta: opcoes.consulta });
|
|
137
|
+
}
|
|
138
|
+
/** O relatorio a partir das paginas do roadmap e dos fatos das threads, de onde quer que venham. */
|
|
139
|
+
function montarStatusDeFatos(docs, fatos, opcoes) {
|
|
140
|
+
const quando = opcoes.quando;
|
|
141
|
+
const porId = new Map(fatos.map(f => [f.id, f]));
|
|
142
|
+
const porItem = new Map();
|
|
143
|
+
for (const f of fatos)
|
|
144
|
+
if (f.roadmap)
|
|
145
|
+
porItem.set(f.roadmap, new Set([...(porItem.get(f.roadmap) ?? []), f.id]));
|
|
146
|
+
const itens = [];
|
|
147
|
+
for (const d of docs.filter(x => x.tipo === 'roadmap')) {
|
|
148
|
+
const estado = mapa(d.dados.estado), sdlc = mapa(d.dados.sdlc);
|
|
149
|
+
const ciclo = texto(estado.ciclo);
|
|
150
|
+
const ligadas = [...new Set([...(porItem.get(d.id) ?? []), ...(texto(sdlc.thread) ? [texto(sdlc.thread)] : [])])]
|
|
151
|
+
.filter(id => porId.has(id)).sort();
|
|
152
|
+
const entregueHoje = ligadas.some(id => porId.get(id).entregueHoje());
|
|
153
|
+
const abertas = ligadas.map(id => porId.get(id)).filter(f => f.aberta);
|
|
154
|
+
let hitl;
|
|
155
|
+
for (const f of abertas) {
|
|
156
|
+
hitl = f.espera();
|
|
157
|
+
if (hitl)
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
const conduz = abertas[0];
|
|
161
|
+
const emAberto = exports.GRUPO_DO_CICLO[ciclo] === 'disponiveis' ? [
|
|
162
|
+
...(texto(estado.exposicao) && texto(estado.exposicao) !== 'Geral' ? [`exposição ${texto(estado.exposicao)}`] : []),
|
|
163
|
+
...(texto(estado.habilitacao) && texto(estado.habilitacao) !== 'Concluída' ? [`habilitação ${texto(estado.habilitacao)}`] : []),
|
|
164
|
+
] : [];
|
|
165
|
+
itens.push({
|
|
166
|
+
id: d.id, titulo: nomeCurto(texto(d.dados.titulo) || d.id), ciclo,
|
|
167
|
+
grupo: entregueHoje ? 'hoje' : exports.GRUPO_DO_CICLO[ciclo] ?? 'proposto',
|
|
168
|
+
emAberto, threads: ligadas,
|
|
169
|
+
...(conduz ? { conduzindo: { thread: conduz.id, fase: conduz.fase, ...(conduz.maquina ? { maquina: conduz.maquina } : {}) } } : {}),
|
|
170
|
+
...(hitl ? { hitl } : {}),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const grupos = exports.GRUPOS_DO_ROADMAP.map(g => ({ ...g, itens: itens.filter(i => i.grupo === g.id) }));
|
|
174
|
+
return {
|
|
175
|
+
contrato: exports.CONTRATO_STATUS_DO_ROADMAP, consultadoEm: quando,
|
|
176
|
+
projeto: opcoes.projeto ?? 'projeto', grupos,
|
|
177
|
+
precisaDeVoce: itens.filter(i => i.hitl).map(i => ({ item: i.id, espera: i.hitl })),
|
|
178
|
+
emSeguida: itens.filter(i => i.conduzindo && !i.hitl).map(i => ({ item: i.id, ...i.conduzindo })),
|
|
179
|
+
...(opcoes.consulta ? { consulta: opcoes.consulta } : {}),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** "orkastery" vira "Orkastery": o titulo diz o nome do projeto como gente escreve. */
|
|
183
|
+
const capitalizar = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
184
|
+
/** A maquina entre parenteses, so quando o fato a traz (visao da rede, D7 da RM-054). */
|
|
185
|
+
const naMaquina = (maquina) => maquina ? ` (${maquina})` : '';
|
|
186
|
+
/**
|
|
187
|
+
* O texto do relatorio, igual em todo canal: os icones sao parte do formato aprovado. `fuso`: o do
|
|
188
|
+
* projeto consultado, na visao da rede; sem ele, o do dono deste processo.
|
|
189
|
+
*/
|
|
190
|
+
function textoDoStatusDoRoadmap(s, fuso) {
|
|
191
|
+
if (s.contrato !== exports.CONTRATO_STATUS_DO_ROADMAP)
|
|
192
|
+
throw new Error('status do roadmap: contrato inválido');
|
|
193
|
+
const p = (0, horario_1.partesLocais)(s.consultadoEm, fuso);
|
|
194
|
+
const linhaDoItem = (i) => {
|
|
195
|
+
const detalhe = i.emAberto.length ? `: ${i.emAberto.join(', ')}` : i.conduzindo && i.grupo !== 'concluidos' && i.grupo !== 'hoje'
|
|
196
|
+
? ` (${i.conduzindo.fase}${i.conduzindo.maquina ? `, ${i.conduzindo.maquina}` : ''})` : '';
|
|
197
|
+
return `• ${i.id} ${i.titulo}${detalhe}${i.hitl ? ' #HITL' : ''}`;
|
|
198
|
+
};
|
|
199
|
+
const cortar = (lista, f) => [...lista.slice(0, exports.TETO_DO_FECHO).map(f), ...(lista.length > exports.TETO_DO_FECHO ? [`• e mais ${lista.length - exports.TETO_DO_FECHO}`] : [])];
|
|
200
|
+
return [
|
|
201
|
+
`Roadmap do ${capitalizar(s.projeto)} (${p.dia}/${p.mes}, ${p.hora}:${p.minuto})`,
|
|
202
|
+
// RM-052: logo abaixo do titulo aprovado, qual projeto foi lido e o que nao foi.
|
|
203
|
+
...(s.consulta ? (0, projeto_alvo_1.linhasDaConsulta)(s.consulta) : []),
|
|
204
|
+
...s.grupos.filter(g => g.itens.length).flatMap(g => ['', `${g.icone} ${g.titulo}`, ...g.itens.map(linhaDoItem)]),
|
|
205
|
+
'', 'O que precisa de você',
|
|
206
|
+
...(s.precisaDeVoce.length ? cortar(s.precisaDeVoce, x => `• ${x.item}${naMaquina(x.espera.maquina)}: ${x.espera.pergunta}` +
|
|
207
|
+
(x.espera.codigo ? ` Responda ${x.espera.codigo} ${x.espera.recomendada ?? 'a'} (ou outra letra).` : ' A pergunta chega no próximo resumo.'))
|
|
208
|
+
: ['• Nada agora.']),
|
|
209
|
+
'', 'O que eu faço em seguida',
|
|
210
|
+
...(s.emSeguida.length ? cortar(s.emSeguida, x => `• ${x.item}: sigo ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}.`)
|
|
211
|
+
: ['• Nada em andamento; sigo o que você priorizar.']),
|
|
212
|
+
].join('\n');
|
|
213
|
+
}
|
package/dist/runtime-ambiente.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ENVS_DE_PROVIDER_PAGO = void 0;
|
|
3
|
+
exports.ENV_THREAD_DO_DESPACHO = exports.ENV_IDENTIDADE_DE_DESPACHO = exports.ENVS_DE_PROVIDER_PAGO = void 0;
|
|
4
4
|
exports.nomesDeProviderAtivos = nomesDeProviderAtivos;
|
|
5
5
|
exports.ambienteDeAssinatura = ambienteDeAssinatura;
|
|
6
6
|
const hitl_public_receipt_1 = require("./hitl-public-receipt");
|
|
@@ -20,6 +20,13 @@ exports.ENVS_DE_PROVIDER_PAGO = [
|
|
|
20
20
|
function nomesDeProviderAtivos(base = process.env) {
|
|
21
21
|
return exports.ENVS_DE_PROVIDER_PAGO.filter(nome => (base[nome] ?? '').trim() !== '');
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Identidade do despacho no ambiente da sessao filha (I-36, D4). RM-037 (defeitosdeco D-4): o domicilio
|
|
25
|
+
* dos nomes e aqui porque nenhum filho de runtime herda a identidade de quem despachou; o despacho
|
|
26
|
+
* acrescenta a sua explicitamente, e o `claude --bg` a leva por sessao em `--settings`.
|
|
27
|
+
*/
|
|
28
|
+
exports.ENV_IDENTIDADE_DE_DESPACHO = 'ORK_DISPATCH_ID';
|
|
29
|
+
exports.ENV_THREAD_DO_DESPACHO = 'ORK_DISPATCH_THREAD';
|
|
23
30
|
/** Copia, sem alterar o ambiente em que as policies ainda precisam ser avaliadas. */
|
|
24
31
|
function ambienteDeAssinatura(base = process.env) {
|
|
25
32
|
const env = { ...base };
|
|
@@ -31,6 +38,10 @@ function ambienteDeAssinatura(base = process.env) {
|
|
|
31
38
|
for (const nome of Object.keys(env))
|
|
32
39
|
if (nome.startsWith('ORK_HITL_'))
|
|
33
40
|
delete env[nome];
|
|
41
|
+
// RM-037 (defeitosdeco D-4): a identidade herdada e de OUTRO despacho (o daemon do `claude --bg`
|
|
42
|
+
// guardava a do primeiro despacho e a passava a todas as sessoes reserva da conta).
|
|
43
|
+
delete env[exports.ENV_IDENTIDADE_DE_DESPACHO];
|
|
44
|
+
delete env[exports.ENV_THREAD_DO_DESPACHO];
|
|
34
45
|
delete env[hitl_public_receipt_1.ENV_HITL_VERIFIERS];
|
|
35
46
|
if (publicVerifiers)
|
|
36
47
|
env[hitl_public_receipt_1.ENV_HITL_VERIFIERS] = publicVerifiers;
|