@orkastery/cli 0.5.3 → 0.5.6
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/claude-code/README.md +158 -0
- package/adapters/claude-code/mods/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
- package/adapters/openclaw/README.md +2 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/dist/adapters/claude-bg.js +77 -13
- package/dist/adapters/codex.js +10 -1
- package/dist/agents-md.js +24 -15
- package/dist/auditrun.js +17 -2
- package/dist/board.js +4 -3
- package/dist/branch-de-estado.js +23 -1
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +13 -7
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +3 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +36 -1
- package/dist/conducao-sentinela.js +388 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +122 -30
- package/dist/conhecimento.js +490 -0
- package/dist/docs.js +22 -8
- package/dist/doctor-pulse-cron.js +182 -27
- package/dist/doctor-rede.js +18 -17
- package/dist/doctor.js +115 -111
- package/dist/entrega-pr.js +22 -21
- package/dist/escopo-escrita.js +10 -1
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +38 -8
- package/dist/fabrica-publicar.js +3 -1
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +1 -1
- package/dist/gates.js +3 -28
- package/dist/handoff.js +10 -6
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-resumo.js +29 -3
- package/dist/hitl-tempo-parado.js +4 -4
- package/dist/hitl.js +29 -33
- package/dist/horario.js +14 -7
- package/dist/hosts.js +52 -1
- package/dist/impedimento.js +108 -14
- package/dist/index.js +623 -521
- package/dist/indice.js +7 -1
- package/dist/init.js +43 -47
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +92 -21
- package/dist/intelligence-graph-contexto.js +56 -17
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/ledger.js +15 -1
- package/dist/liveness.js +6 -3
- package/dist/locale-ajuda.js +761 -0
- package/dist/locale-gerados.js +160 -0
- package/dist/locale-master.js +99 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-pulse.js +211 -0
- package/dist/locale-retry.js +385 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-ship.js +494 -0
- package/dist/locale-thread.js +277 -0
- package/dist/locale-verify.js +125 -0
- package/dist/locale.js +714 -0
- package/dist/manifest.js +129 -3
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +4 -6
- package/dist/master.js +21 -24
- package/dist/mcp-artifacts.js +11 -2
- package/dist/mcp-git.js +23 -6
- package/dist/mcp-grafo.js +7 -4
- package/dist/mcp-server.js +8 -2
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +5 -2
- package/dist/noctum-arco.js +911 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-envelope.js +810 -0
- package/dist/noctum-execucao.js +304 -0
- package/dist/noctum-fila.js +280 -0
- package/dist/ocupacao.js +29 -2
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +9 -2
- package/dist/orquestracao.js +13 -12
- package/dist/parado-no-condutor.js +34 -9
- package/dist/phase.js +94 -39
- package/dist/policies.js +4 -4
- package/dist/portfolio.js +4 -2
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +127 -4
- package/dist/prompts.js +6 -2
- package/dist/pulse-delivery.js +13 -2
- package/dist/pulse.js +52 -19
- package/dist/ratelimit.js +4 -2
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-status.js +3 -2
- package/dist/retry.js +232 -148
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +129 -33
- package/dist/runtime-context.js +181 -3
- package/dist/runtimes.js +2 -1
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +7 -1
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +1 -1
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +84 -81
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +153 -82
- package/dist/util.js +8 -1
- package/dist/verify.js +141 -52
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +165 -0
- package/dist/worktree.js +20 -0
- package/dist/yaml.js +27 -8
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
package/dist/entrega-pr.js
CHANGED
|
@@ -24,6 +24,7 @@ const ledger_1 = require("./ledger");
|
|
|
24
24
|
const modos_1 = require("./modos");
|
|
25
25
|
const thread_1 = require("./thread");
|
|
26
26
|
const util_1 = require("./util");
|
|
27
|
+
const locale_1 = require("./locale");
|
|
27
28
|
/** O merge `ship(<thread>)` mais recente na base remota, com o head do PR (segundo pai). */
|
|
28
29
|
function mergeDaEntrega(raiz, thread, remoto, base) {
|
|
29
30
|
const r = (0, util_1.exec)('git', ['log', `refs/remotes/${remoto}/${base}`, '--first-parent', '--format=%H%x09%P%x09%s', `--grep=^ship(${thread})`], raiz);
|
|
@@ -64,32 +65,32 @@ function registrarEntregaPorPr(carregado, threadId, opcoes = {}) {
|
|
|
64
65
|
const base = carregado.manifesto.worktree.base_branch;
|
|
65
66
|
const thread = (0, thread_1.lerThread)(raiz, threadId);
|
|
66
67
|
if (thread.status === 'fechada')
|
|
67
|
-
return resultado(threadId, 'ja-registrada',
|
|
68
|
+
return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prThreadFechada));
|
|
68
69
|
if (opcoes.buscar !== false)
|
|
69
70
|
(0, util_1.exec)('git', ['fetch', '-q', '--', remoto, `+refs/heads/${base}:refs/remotes/${remoto}/${base}`], raiz);
|
|
70
71
|
const encontrado = mergeDaEntrega(raiz, threadId, remoto, base);
|
|
71
72
|
if (!encontrado)
|
|
72
|
-
return resultado(threadId, 'sem-merge',
|
|
73
|
+
return resultado(threadId, 'sem-merge', (0, locale_1.textoDoCatalogo)((m) => m.ship.prSemMerge(threadId, remoto, base)));
|
|
73
74
|
// RM-044 (A3): o condutor roda o registrar-pr logo depois do merge; e aqui que ele fica sabendo da pagina que
|
|
74
75
|
// vai deixar o push da base vermelho, lida da base remota que o fetch acabou de trazer.
|
|
75
76
|
const merge = { ...encontrado, docsPendentes: (0, docs_1.docsPendentesDoMerge)(raiz, threadId, `refs/remotes/${remoto}/${base}`) };
|
|
76
77
|
const dir = (0, thread_1.dirThread)(raiz, threadId);
|
|
77
78
|
const jaTem = (0, ledger_1.lerLedger)(dir).some((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.shipConcluido && e.mergeSha === merge.mergeSha);
|
|
78
79
|
if (jaTem)
|
|
79
|
-
return resultado(threadId, 'ja-registrada',
|
|
80
|
+
return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prJaTemShipDone), merge);
|
|
80
81
|
// A ponta do remoto AGORA, e o merge dentro dela: e o que torna o push provado, nao presumido.
|
|
81
82
|
const remotoAgora = (0, util_1.exec)('git', ['ls-remote', '--', remoto, `refs/heads/${base}`], raiz);
|
|
82
83
|
const ponta = remotoAgora.ok ? remotoAgora.stdout.split('\t')[0].trim() : '';
|
|
83
84
|
if (!ponta || !(0, util_1.exec)('git', ['merge-base', '--is-ancestor', merge.mergeSha, ponta], raiz).ok) {
|
|
84
|
-
return resultado(threadId, 'recusada',
|
|
85
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prForaDaPonta(merge.mergeSha.slice(0, 7), remoto, base)), merge);
|
|
85
86
|
}
|
|
86
87
|
const ci = (0, ci_1.consultarCi)(carregado, merge.headSha ?? merge.mergeSha, remoto, opcoes.executorCi);
|
|
87
88
|
if (ci.required && !ci.ok) {
|
|
88
|
-
return resultado(threadId, 'recusada',
|
|
89
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prSemCiVerde((0, locale_1.remontar)(ci.detail))), { ...merge, ci });
|
|
89
90
|
}
|
|
90
91
|
// RM-037 (fatia 3, defeito 6): o ensaio confere tudo e para aqui, sem ledger, fase nem fabrica.
|
|
91
92
|
if (opcoes.dryRun) {
|
|
92
|
-
return resultado(threadId, 'registraria',
|
|
93
|
+
return resultado(threadId, 'registraria', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRegistraria(merge.mergeSha.slice(0, 7))), { ...merge, ci });
|
|
93
94
|
}
|
|
94
95
|
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipConcluido, {
|
|
95
96
|
de: thread.base?.branch ?? null,
|
|
@@ -114,7 +115,7 @@ function registrarEntregaPorPr(carregado, threadId, opcoes = {}) {
|
|
|
114
115
|
avancarParaShip(raiz, threadId);
|
|
115
116
|
if (opcoes.publicar !== false)
|
|
116
117
|
(0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
|
|
117
|
-
return resultado(threadId, 'registrou',
|
|
118
|
+
return resultado(threadId, 'registrou', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRegistrou(merge.mergeSha.slice(0, 7))), { ...merge, ci });
|
|
118
119
|
}
|
|
119
120
|
const ghApi = (caminho) => (0, util_1.exec)('gh', ['api', caminho, '-H', 'Accept: application/vnd.github+json'], process.cwd(), 120000);
|
|
120
121
|
function lerJson(r) {
|
|
@@ -139,25 +140,25 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
|
|
|
139
140
|
const gh = opcoes.executorGitHub ?? ghApi;
|
|
140
141
|
const declarados = carregado.manifesto.ci.external_repositories;
|
|
141
142
|
if (!Object.hasOwn(declarados, repo)) {
|
|
142
|
-
return resultado(threadId, 'recusada',
|
|
143
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRepoNaoDeclarado(repo)));
|
|
143
144
|
}
|
|
144
145
|
if (!Number.isSafeInteger(n) || n < 1)
|
|
145
|
-
return resultado(threadId, 'recusada',
|
|
146
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prNumeroInvalido(String(n))));
|
|
146
147
|
const thread = (0, thread_1.lerThread)(raiz, threadId);
|
|
147
148
|
if (thread.status === 'fechada')
|
|
148
|
-
return resultado(threadId, 'ja-registrada',
|
|
149
|
+
return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prThreadFechada));
|
|
149
150
|
const dir = (0, thread_1.dirThread)(raiz, threadId);
|
|
150
151
|
if ((0, ledger_1.lerLedger)(dir).some((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.shipConcluido && e.repositorio === repo && e.pr === n)) {
|
|
151
|
-
return resultado(threadId, 'ja-registrada',
|
|
152
|
+
return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoJaTem(n, repo)));
|
|
152
153
|
}
|
|
153
154
|
const pr = lerJson(gh(`repos/${repo}/pulls/${n}`));
|
|
154
155
|
if (!pr)
|
|
155
|
-
return resultado(threadId, 'recusada',
|
|
156
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prConsultaFalhou(n, repo)));
|
|
156
157
|
const mergeSha = typeof pr.merge_commit_sha === 'string' && /^[0-9a-f]{40}$/.test(pr.merge_commit_sha) ? pr.merge_commit_sha : null;
|
|
157
158
|
const headSha = typeof pr.head?.sha === 'string' && /^[0-9a-f]{40}$/.test(pr.head.sha) ? pr.head.sha : null;
|
|
158
159
|
const base = typeof pr.base?.ref === 'string' && /^[A-Za-z0-9._/-]{1,200}$/.test(pr.base.ref) ? pr.base.ref : null;
|
|
159
160
|
if (pr.merged !== true || !mergeSha || !headSha || !base) {
|
|
160
|
-
return resultado(threadId, 'sem-merge',
|
|
161
|
+
return resultado(threadId, 'sem-merge', (0, locale_1.textoDoCatalogo)((m) => m.ship.prNaoMesclado(n, repo)));
|
|
161
162
|
}
|
|
162
163
|
// Achado A4 do CHECK: o PR precisa ser DESTA thread e ter entrado na branch padrao do repositorio. Sem o
|
|
163
164
|
// vinculo, qualquer PR mesclado do repositorio viraria entrega de qualquer thread; sem a base, um PR
|
|
@@ -167,28 +168,28 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
|
|
|
167
168
|
// letra ou digito colado nao (outro id). Sem diferenciar maiusculas.
|
|
168
169
|
.some((x) => new RegExp(`(^|[^a-z0-9-])${threadId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![a-z0-9])`, 'i').test(x));
|
|
169
170
|
if (!vinculo) {
|
|
170
|
-
return resultado(threadId, 'recusada',
|
|
171
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prNaoCitaThread(n, repo, threadId)), { mergeSha, headSha });
|
|
171
172
|
}
|
|
172
173
|
const repositorio = lerJson(gh(`repos/${repo}`));
|
|
173
174
|
if (!repositorio)
|
|
174
|
-
return resultado(threadId, 'recusada',
|
|
175
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRepoFalhou(repo)), { mergeSha, headSha });
|
|
175
176
|
if (repositorio.default_branch !== base) {
|
|
176
|
-
return resultado(threadId, 'recusada',
|
|
177
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prOutraBase(n, base, repo, repositorio.default_branch)), { mergeSha, headSha });
|
|
177
178
|
}
|
|
178
179
|
// A ponta da base AGORA, e o merge dentro dela: o push provado, pela mesma pergunta do ls-remote.
|
|
179
180
|
const ramo = lerJson(gh(`repos/${repo}/branches/${encodeURIComponent(base)}`));
|
|
180
181
|
const ponta = typeof ramo?.commit?.sha === 'string' && /^[0-9a-f]{40}$/.test(ramo.commit.sha) ? ramo.commit.sha : '';
|
|
181
182
|
const comparacao = ponta ? lerJson(gh(`repos/${repo}/compare/${mergeSha}...${ponta}`)) : null;
|
|
182
183
|
if (!ponta || !comparacao || !['ahead', 'identical'].includes(String(comparacao.status))) {
|
|
183
|
-
return resultado(threadId, 'recusada',
|
|
184
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoForaDaPonta(mergeSha.slice(0, 7), repo, base)), { mergeSha, headSha });
|
|
184
185
|
}
|
|
185
186
|
const check = declarados[repo];
|
|
186
187
|
const ci = check ? (0, ci_1.consultarCiDoRepositorio)(repo, headSha, check, opcoes.executorCi) : null;
|
|
187
188
|
if (ci && !ci.ok) {
|
|
188
|
-
return resultado(threadId, 'recusada',
|
|
189
|
+
return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prSemCiVerde((0, locale_1.remontar)(ci.detail))), { mergeSha, headSha, ci });
|
|
189
190
|
}
|
|
190
191
|
if (opcoes.dryRun) {
|
|
191
|
-
return resultado(threadId, 'registraria',
|
|
192
|
+
return resultado(threadId, 'registraria', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoRegistraria(mergeSha.slice(0, 7), n, repo)), { mergeSha, headSha, ci });
|
|
192
193
|
}
|
|
193
194
|
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipConcluido, {
|
|
194
195
|
de: thread.base?.branch ?? null,
|
|
@@ -215,7 +216,7 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
|
|
|
215
216
|
avancarParaShip(raiz, threadId);
|
|
216
217
|
if (opcoes.publicar !== false)
|
|
217
218
|
(0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
|
|
218
|
-
return resultado(threadId, 'registrou',
|
|
219
|
+
return resultado(threadId, 'registrou', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoRegistrou(mergeSha.slice(0, 7), n, repo)), { mergeSha, headSha, ci });
|
|
219
220
|
}
|
|
220
221
|
/** `ork ship registrar-pr --todas`: toda thread aberta que ja tem merge `ship(<thread>)` na base. */
|
|
221
222
|
function registrarEntregasPorPr(carregado, opcoes = {}) {
|
|
@@ -230,7 +231,7 @@ function registrarEntregasPorPr(carregado, opcoes = {}) {
|
|
|
230
231
|
return registrarEntregaPorPr(carregado, id, { ...opcoes, buscar: false, publicar: false });
|
|
231
232
|
}
|
|
232
233
|
catch (e) {
|
|
233
|
-
return resultado(id, 'recusada', e.message);
|
|
234
|
+
return resultado(id, 'recusada', e instanceof locale_1.ErroDoCatalogo ? (0, locale_1.textoDoCatalogo)(e.montar) : e.message);
|
|
234
235
|
}
|
|
235
236
|
});
|
|
236
237
|
// Uma publicacao so para o lote inteiro, nao uma por thread.
|
package/dist/escopo-escrita.js
CHANGED
|
@@ -60,7 +60,16 @@ function exigirEscopoDeEscrita(threads) {
|
|
|
60
60
|
/** Um nome autorizado nao pode ser alias de outra thread ou de estado externo. */
|
|
61
61
|
function validarDiretorioDeThread(raiz, id) {
|
|
62
62
|
validarThreadDeEscrita(id);
|
|
63
|
-
|
|
63
|
+
let dir;
|
|
64
|
+
// RM-047: o estadoCanonico já recusa link (estado.link); aqui a recusa mantém o código do escopo.
|
|
65
|
+
try {
|
|
66
|
+
dir = (0, estado_thread_1.estadoCanonico)(raiz, id);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
if (e.message.startsWith('estado.link'))
|
|
70
|
+
throw new Error('scope.thread.alias');
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
64
73
|
for (const candidato of [path.dirname(path.dirname(dir)), path.dirname(dir), dir]) {
|
|
65
74
|
const stat = fs.lstatSync(candidato, { throwIfNoEntry: false });
|
|
66
75
|
if (!stat)
|
package/dist/estado-thread.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.raizDoEstado = raizDoEstado;
|
|
37
37
|
exports.estadoCanonico = estadoCanonico;
|
|
38
|
+
exports.exigirEstadoSemLink = exigirEstadoSemLink;
|
|
38
39
|
exports.vincularEstado = vincularEstado;
|
|
39
40
|
exports.comEstadoParaGit = comEstadoParaGit;
|
|
40
41
|
exports.auditarEstado = auditarEstado;
|
|
@@ -66,7 +67,28 @@ function raizDoEstado(raiz) {
|
|
|
66
67
|
function estadoCanonico(raiz, id) {
|
|
67
68
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(id))
|
|
68
69
|
throw new Error('id de thread inválido');
|
|
69
|
-
|
|
70
|
+
const principal = raizDoEstado(raiz);
|
|
71
|
+
exigirEstadoSemLink(principal, ['.orkastery', 'threads', id]);
|
|
72
|
+
return path.join(principal, '.orkastery', 'threads', id);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* RM-047 (fronteira de confiança): na árvore principal, `.orkastery`, `threads` e o diretório da thread
|
|
76
|
+
* são pastas de verdade. Um link versionado no clone levaria ledger, claims e thread.json para fora da
|
|
77
|
+
* raiz, e o `ork` anexaria linhas em arquivo alheio. O link que o próprio `ork` cria fica na worktree
|
|
78
|
+
* (`vincularEstado`) e aponta para cá; ele nunca é um destes componentes.
|
|
79
|
+
*/
|
|
80
|
+
function exigirEstadoSemLink(principal, componentes) {
|
|
81
|
+
let atual = principal;
|
|
82
|
+
for (const nome of componentes) {
|
|
83
|
+
atual = path.join(atual, nome);
|
|
84
|
+
const stat = fs.lstatSync(atual, { throwIfNoEntry: false });
|
|
85
|
+
if (!stat)
|
|
86
|
+
return;
|
|
87
|
+
if (stat.isSymbolicLink()) {
|
|
88
|
+
throw new Error(`estado.link: ${path.relative(principal, atual)} é link simbólico; o estado do ork fica em ` +
|
|
89
|
+
'pastas de verdade dentro da raiz (um link versionado levaria a escrita para fora dela). Remova o link.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
70
92
|
}
|
|
71
93
|
function mesmoArquivo(a, b) {
|
|
72
94
|
return fs.existsSync(a) && fs.existsSync(b) && fs.realpathSync(a) === fs.realpathSync(b);
|
|
@@ -41,10 +41,14 @@ exports.aplicarExperiencia = aplicarExperiencia;
|
|
|
41
41
|
const fs = __importStar(require("node:fs"));
|
|
42
42
|
const path = __importStar(require("node:path"));
|
|
43
43
|
const node_crypto_1 = require("node:crypto");
|
|
44
|
+
const locale_1 = require("./locale");
|
|
44
45
|
exports.INICIO_EXPERIENCIA = '<!-- orkastery:experiencia:begin -->';
|
|
45
46
|
exports.FIM_EXPERIENCIA = '<!-- orkastery:experiencia:end -->';
|
|
46
47
|
const CONTRATO = 'ork.experiencia-install/v1';
|
|
47
|
-
|
|
48
|
+
/** RM-061 (F3a, D8): as linguas em que o bloco e escrito e, sem recibo, reconhecido. */
|
|
49
|
+
const LOCALES_DO_BLOCO = ['pt-BR', 'en'];
|
|
50
|
+
/** Marca do diretorio no molde da linha: o texto antes e depois dela reconhece a linha em cada lingua. */
|
|
51
|
+
const MARCA_DO_DIRETORIO = '{{diretorio}}';
|
|
48
52
|
const conflito = () => { throw Error('experiencia.bloco.conflict: bloco ou recibo alterado/duplicado; preserve os arquivos e revise a instalação'); };
|
|
49
53
|
/** Recusa symlinks e hardlinks em qualquer componente antes da leitura/escrita. */
|
|
50
54
|
function caminhoSeguro(projeto, arquivo) {
|
|
@@ -83,18 +87,35 @@ function decodificar(s) {
|
|
|
83
87
|
return b;
|
|
84
88
|
}
|
|
85
89
|
const eolDe = (b) => b.includes('\r\n') ? '\r\n' : '\n';
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
/**
|
|
91
|
+
* O caminho vai relativo ao projeto: o arquivo de instruções costuma ser versionado. RM-061 (F3a, D1 e D2): o texto
|
|
92
|
+
* sai na língua pedida, pela família `gerados` do catálogo; os marcadores são os mesmos nas duas línguas.
|
|
93
|
+
*/
|
|
94
|
+
function nucleoDoBloco(skill, eol, locale) {
|
|
95
|
+
const t = (0, locale_1.msg)(locale).gerados.experiencia;
|
|
96
|
+
return Buffer.from([exports.INICIO_EXPERIENCIA, `## ${t.titulo}`, t.consulte, t.diretorio(JSON.stringify(skill)), t.preferencias,
|
|
93
97
|
exports.FIM_EXPERIENCIA, ''].join(eol));
|
|
94
98
|
}
|
|
99
|
+
/** O diretório escrito na linha do bloco de uma língua, ou null se a linha não é a dessa língua. */
|
|
100
|
+
function diretorioDaLinha(linha, locale) {
|
|
101
|
+
const molde = (0, locale_1.msg)(locale).gerados.experiencia.diretorio(MARCA_DO_DIRETORIO);
|
|
102
|
+
const corte = molde.indexOf(MARCA_DO_DIRETORIO);
|
|
103
|
+
const antes = molde.slice(0, corte), depois = molde.slice(corte + MARCA_DO_DIRETORIO.length);
|
|
104
|
+
if (linha.length < antes.length + depois.length || !linha.startsWith(antes) || !linha.endsWith(depois))
|
|
105
|
+
return null;
|
|
106
|
+
let skill;
|
|
107
|
+
try {
|
|
108
|
+
skill = JSON.parse(linha.slice(antes.length, linha.length - depois.length));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return typeof skill === 'string' && !/[\r\n`]/.test(skill) ? skill : null;
|
|
114
|
+
}
|
|
95
115
|
/**
|
|
96
116
|
* Bloco sem recibo (clone novo, `.orkastery/` apagado) só é adotado quando é exatamente o texto
|
|
97
|
-
* que esta versão ou a anterior geram, com qualquer diretório
|
|
117
|
+
* que esta versão ou a anterior geram, com qualquer diretório e em qualquer das duas línguas (RM-061,
|
|
118
|
+
* F3a, D8); qualquer outra edição é conflito.
|
|
98
119
|
*/
|
|
99
120
|
function reconhecerBloco(antes) {
|
|
100
121
|
if (contar(antes, exports.INICIO_EXPERIENCIA) !== 1 || contar(antes, exports.FIM_EXPERIENCIA) !== 1)
|
|
@@ -108,18 +129,14 @@ function reconhecerBloco(antes) {
|
|
|
108
129
|
fim += eol.length;
|
|
109
130
|
const texto = antes.subarray(inicio, fim);
|
|
110
131
|
const linha = texto.toString('utf8').split(eol)[3] ?? '';
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (typeof skill !== 'string' || /[\r\n`]/.test(skill))
|
|
120
|
-
return null;
|
|
121
|
-
const esperado = nucleoDoBloco(skill, eol);
|
|
122
|
-
if (!texto.equals(esperado) && !texto.equals(esperado.subarray(0, esperado.length - eol.length)))
|
|
132
|
+
const reconhecido = LOCALES_DO_BLOCO.some((locale) => {
|
|
133
|
+
const skill = diretorioDaLinha(linha, locale);
|
|
134
|
+
if (skill === null)
|
|
135
|
+
return false;
|
|
136
|
+
const esperado = nucleoDoBloco(skill, eol, locale);
|
|
137
|
+
return texto.equals(esperado) || texto.equals(esperado.subarray(0, esperado.length - eol.length));
|
|
138
|
+
});
|
|
139
|
+
if (!reconhecido)
|
|
123
140
|
return null;
|
|
124
141
|
// Uma linha em branco antes do bloco é o separador que a instalação põe depois de uma linha.
|
|
125
142
|
const sep = Buffer.from(eol);
|
|
@@ -144,8 +161,11 @@ function removerBloco(antes, bloco) {
|
|
|
144
161
|
const juncao = prefixo.length && prefixo.at(-1) !== 10 && sufixo.length ? Buffer.from(bloco.eol) : Buffer.alloc(0);
|
|
145
162
|
return Buffer.concat([prefixo, juncao, sufixo]);
|
|
146
163
|
}
|
|
147
|
-
/**
|
|
148
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Planejamento puro em relação ao disco: dry-run e preflight usam o mesmo contrato. O bloco sai no `locale` pedido
|
|
166
|
+
* (RM-061, F3a), pt-BR por omissão: só o `ork adapter install` passa a língua do dono (D2).
|
|
167
|
+
*/
|
|
168
|
+
function planejarExperiencia(projeto, host, skill, locale = locale_1.LOCALE_PADRAO) {
|
|
149
169
|
projeto = fs.realpathSync(path.resolve(projeto));
|
|
150
170
|
if (!['claude-code', 'codex'].includes(host))
|
|
151
171
|
throw Error('experiencia.host.unsupported');
|
|
@@ -189,7 +209,7 @@ function planejarExperiencia(projeto, host, skill) {
|
|
|
189
209
|
let separador, nucleo, anterior;
|
|
190
210
|
if (antes && bloco) {
|
|
191
211
|
// Atualização no lugar: o que está antes e depois do bloco, inclusive edição externa, fica.
|
|
192
|
-
nucleo = nucleoDoBloco(skill, bloco.eol);
|
|
212
|
+
nucleo = nucleoDoBloco(skill, bloco.eol, locale);
|
|
193
213
|
separador = bloco.separador;
|
|
194
214
|
depois = Buffer.concat([antes.subarray(0, bloco.inicio), nucleo, antes.subarray(bloco.fim)]);
|
|
195
215
|
// Adotado sem recibo: se só havia o bloco, o arquivo conta como criado pelo pacote.
|
|
@@ -198,7 +218,7 @@ function planejarExperiencia(projeto, host, skill) {
|
|
|
198
218
|
}
|
|
199
219
|
else {
|
|
200
220
|
const base = antes ?? Buffer.alloc(0), eol = eolDe(base);
|
|
201
|
-
nucleo = nucleoDoBloco(skill, eol);
|
|
221
|
+
nucleo = nucleoDoBloco(skill, eol, locale);
|
|
202
222
|
separador = Buffer.from(base.length ? (base.at(-1) === 10 ? eol : eol + eol) : '');
|
|
203
223
|
depois = Buffer.concat([base, separador, nucleo]);
|
|
204
224
|
anterior = antes?.toString('base64') ?? null;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CRONTAB_DO_SISTEMA = exports.MARCA_DA_BATIDA = exports.ultimaRodadaDaVarredura = exports.ATRASO_DA_BATIDA_MIN = void 0;
|
|
37
|
+
exports.gravarCrontabDoSistema = gravarCrontabDoSistema;
|
|
38
|
+
exports.caminhoDaBatida = caminhoDaBatida;
|
|
39
|
+
exports.scriptDaBatida = scriptDaBatida;
|
|
40
|
+
exports.linhaDaBatida = linhaDaBatida;
|
|
41
|
+
exports.linhaGerenciada = linhaGerenciada;
|
|
42
|
+
exports.estadoDaBatida = estadoDaBatida;
|
|
43
|
+
exports.instalarBatida = instalarBatida;
|
|
44
|
+
exports.removerBatida = removerBatida;
|
|
45
|
+
exports.textoDoEstadoDaBatida = textoDoEstadoDaBatida;
|
|
46
|
+
exports.textoDoResultadoDaBatida = textoDoResultadoDaBatida;
|
|
47
|
+
/**
|
|
48
|
+
* RM-063 (F1): a batida da fabrica, instalada e vigiada pelo proprio `ork`.
|
|
49
|
+
*
|
|
50
|
+
* Incidente do maquina-b, de 03/10 a 12/10/2026 (P1 e P7 da pagina da RM-063): o cron da varredura do
|
|
51
|
+
* pulse nunca foi instalado, o `ork doctor` so conferia o crontab com `pulse-host.json` e, sem
|
|
52
|
+
* transporte, calava; a correcao (editar o crontab) foi barrada como persistencia nao autorizada. A
|
|
53
|
+
* maquina ficou nove dias sem batida e ninguem soube.
|
|
54
|
+
*
|
|
55
|
+
* `ork fabrica batida instalar|status|remover` e o comando que o dono autoriza uma vez: instala a linha
|
|
56
|
+
* do template `monitor/pulse.cron` com o checkout canonico e o PATH que acha o `node`, o `claude` e o
|
|
57
|
+
* transporte, preserva as outras entradas do crontab e grava o evento no ledger da maquina
|
|
58
|
+
* (`~/.orkastery/ledger.jsonl`). A linha e marcada (`# ork-batida`) e diz o projeto
|
|
59
|
+
* (`ORK_PULSE_PROJECT`): e por elas que o `remover` tira so a linha dela.
|
|
60
|
+
*
|
|
61
|
+
* O crontab nunca e gravado sem ter sido lido, e a gravacao so vale depois de relida. O executor e
|
|
62
|
+
* injetado: os testes usam um crontab falso e nunca tocam no da maquina.
|
|
63
|
+
*/
|
|
64
|
+
const fs = __importStar(require("node:fs"));
|
|
65
|
+
const path = __importStar(require("node:path"));
|
|
66
|
+
const node_child_process_1 = require("node:child_process");
|
|
67
|
+
const doctor_pulse_cron_1 = require("./doctor-pulse-cron");
|
|
68
|
+
var doctor_pulse_cron_2 = require("./doctor-pulse-cron");
|
|
69
|
+
Object.defineProperty(exports, "ATRASO_DA_BATIDA_MIN", { enumerable: true, get: function () { return doctor_pulse_cron_2.ATRASO_DA_BATIDA_MIN; } });
|
|
70
|
+
Object.defineProperty(exports, "ultimaRodadaDaVarredura", { enumerable: true, get: function () { return doctor_pulse_cron_2.ultimaRodadaDaVarredura; } });
|
|
71
|
+
const estado_thread_1 = require("./estado-thread");
|
|
72
|
+
const ledger_1 = require("./ledger");
|
|
73
|
+
const maquina_1 = require("./maquina");
|
|
74
|
+
const horario_1 = require("./horario");
|
|
75
|
+
const util_1 = require("./util");
|
|
76
|
+
const procedencia_1 = require("./procedencia");
|
|
77
|
+
/** O comentario no fim da linha que o `ork` instalou; o `sh` do cron o ignora. */
|
|
78
|
+
exports.MARCA_DA_BATIDA = '# ork-batida';
|
|
79
|
+
/** Os binarios que a varredura chama, alem do `node`: o runtime, a forja e o git. */
|
|
80
|
+
const BINARIOS_DA_VARREDURA = ['claude', 'codex', 'gh', 'git'];
|
|
81
|
+
const PATH_DO_SISTEMA = ['/usr/local/bin', '/usr/bin', '/bin'];
|
|
82
|
+
/** `crontab -` com o texto no stdin, sem shell e com prazo. */
|
|
83
|
+
function gravarCrontabDoSistema(texto) {
|
|
84
|
+
const r = (0, node_child_process_1.spawnSync)('crontab', ['-'], { input: texto, encoding: 'utf8', timeout: 5000 });
|
|
85
|
+
if (r.status === 0)
|
|
86
|
+
return { ok: true };
|
|
87
|
+
const erro = r.error ? r.error.code ?? r.error.message : null;
|
|
88
|
+
if (erro === 'ENOENT')
|
|
89
|
+
return { ok: false, motivo: 'comando crontab ausente nesta maquina' };
|
|
90
|
+
const primeira = `${r.stderr ?? ''}${r.stdout ?? ''}`.trim().split('\n')[0] ?? '';
|
|
91
|
+
return { ok: false, motivo: primeira ? `crontab - falhou: ${primeira}` : `crontab - saiu ${r.status ?? erro ?? 'sem codigo'}` };
|
|
92
|
+
}
|
|
93
|
+
exports.CRONTAB_DO_SISTEMA = { ler: doctor_pulse_cron_1.lerCrontabDoSistema, gravar: gravarCrontabDoSistema };
|
|
94
|
+
/** Caminho que entra na linha do cron: absoluto, sem `%` (o cron o troca por quebra de linha) e sem controle. */
|
|
95
|
+
function caminhoAceito(caminho) {
|
|
96
|
+
return path.isAbsolute(caminho) && !/[%\p{Cc}]/u.test(caminho);
|
|
97
|
+
}
|
|
98
|
+
/** Diretorio aceito no PATH da linha: alem do caminho aceito, sem `:`, o separador do PATH. */
|
|
99
|
+
function diretorioAceito(dir) {
|
|
100
|
+
return caminhoAceito(dir) && !dir.includes(':');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* O executavel do transporte, so do `pulse-host.json` local desta maquina (RM-047, P2): o versionado no git, ou o link,
|
|
104
|
+
* nao poe diretorio nenhum no PATH da batida.
|
|
105
|
+
*/
|
|
106
|
+
function transporteDoProjeto(raiz, dirMonitor) {
|
|
107
|
+
const arquivo = path.join(dirMonitor, 'pulse-host.json');
|
|
108
|
+
try {
|
|
109
|
+
(0, procedencia_1.exigirHostLocal)(raiz, arquivo);
|
|
110
|
+
const config = JSON.parse(fs.readFileSync(arquivo, 'utf8'));
|
|
111
|
+
return typeof config.executavel === 'string' && config.executavel.trim() ? config.executavel.trim() : null;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* O PATH da linha: o diretorio do `node` que roda este `ork`, os do `claude`, `codex`, `gh` e `git` achados
|
|
119
|
+
* agora, o do executavel do transporte (`pulse-host.json`) e o PATH minimo do sistema. O cron nao carrega o
|
|
120
|
+
* perfil do shell; sem isto a varredura nao acha o runtime nem o transporte (o template avisa).
|
|
121
|
+
*/
|
|
122
|
+
function caminhoDaBatida(opcoes = {}) {
|
|
123
|
+
const achar = opcoes.achar ?? ((bin) => (0, util_1.noPath)(bin));
|
|
124
|
+
const dirs = [];
|
|
125
|
+
const somar = (arquivo) => {
|
|
126
|
+
if (!arquivo || !path.isAbsolute(arquivo))
|
|
127
|
+
return;
|
|
128
|
+
const dir = path.dirname(arquivo);
|
|
129
|
+
if (diretorioAceito(dir) && !dirs.includes(dir))
|
|
130
|
+
dirs.push(dir);
|
|
131
|
+
};
|
|
132
|
+
somar(opcoes.node ?? process.execPath);
|
|
133
|
+
for (const bin of BINARIOS_DA_VARREDURA)
|
|
134
|
+
somar(achar(bin));
|
|
135
|
+
if (opcoes.transporte)
|
|
136
|
+
somar(path.isAbsolute(opcoes.transporte) ? opcoes.transporte : achar(opcoes.transporte));
|
|
137
|
+
for (const dir of PATH_DO_SISTEMA)
|
|
138
|
+
if (!dirs.includes(dir))
|
|
139
|
+
dirs.push(dir);
|
|
140
|
+
return dirs.join(':');
|
|
141
|
+
}
|
|
142
|
+
/** O script da varredura que a linha roda: o do checkout do projeto, senao o do `ork` instalado. */
|
|
143
|
+
function scriptDaBatida(raiz, scriptDoOrk = (0, doctor_pulse_cron_1.scriptDaVarreduraDoOrk)()) {
|
|
144
|
+
const doProjeto = path.join(raiz, 'monitor', doctor_pulse_cron_1.SCRIPT_DA_VARREDURA);
|
|
145
|
+
return fs.existsSync(doProjeto) ? doProjeto : scriptDoOrk;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* A linha que o `ork` instala: a batida do template (`*\/15`), o projeto alvo, o PATH e o script, com a
|
|
149
|
+
* marca no fim. `raiz` e a raiz canonica (o checkout principal, nunca a worktree de uma thread).
|
|
150
|
+
*/
|
|
151
|
+
function linhaDaBatida(raiz, opcoes = {}) {
|
|
152
|
+
const script = scriptDaBatida(raiz, opcoes.scriptDoOrk === undefined ? (0, doctor_pulse_cron_1.scriptDaVarreduraDoOrk)() : opcoes.scriptDoOrk);
|
|
153
|
+
if (!script) {
|
|
154
|
+
throw new Error(`fabrica.batida.sem-script: nem ${path.join(raiz, 'monitor', doctor_pulse_cron_1.SCRIPT_DA_VARREDURA)} nem o ` +
|
|
155
|
+
`${doctor_pulse_cron_1.SCRIPT_DA_VARREDURA} do ork instalado existem; reinstale o ork (npm i -g @orkastery/cli)`);
|
|
156
|
+
}
|
|
157
|
+
for (const caminho of [raiz, script]) {
|
|
158
|
+
if (!caminhoAceito(caminho)) {
|
|
159
|
+
throw new Error(`fabrica.batida.caminho-invalido: ${JSON.stringify(caminho)} nao cabe numa linha do cron ` +
|
|
160
|
+
'(precisa ser absoluto, sem % e sem caractere de controle)');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const PATH = caminhoDaBatida({ node: opcoes.node, achar: opcoes.achar,
|
|
164
|
+
transporte: transporteDoProjeto(raiz, path.join(raiz, '.orkastery', 'monitor')) });
|
|
165
|
+
return `${doctor_pulse_cron_1.BATIDA_DO_TEMPLATE} ORK_PULSE_PROJECT=${(0, doctor_pulse_cron_1.citarNoShell)(raiz)} PATH=${(0, doctor_pulse_cron_1.citarNoShell)(PATH)} ` +
|
|
166
|
+
`${(0, doctor_pulse_cron_1.citarNoShell)(script)} ${exports.MARCA_DA_BATIDA}`;
|
|
167
|
+
}
|
|
168
|
+
/** A linha foi instalada pelo `ork fabrica batida instalar` (traz a marca no fim)? */
|
|
169
|
+
function linhaGerenciada(linha) {
|
|
170
|
+
return linha.trimEnd().endsWith(exports.MARCA_DA_BATIDA);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* As entradas da varredura DESTE projeto, separadas pela marca. O projeto efetivo considera a linha de ambiente do
|
|
174
|
+
* crontab acima da entrada: o template abaixo de `ORK_PULSE_PROJECT=/outro` e a batida do outro, e fica.
|
|
175
|
+
*/
|
|
176
|
+
function separar(texto, raiz) {
|
|
177
|
+
const doProjeto = (0, doctor_pulse_cron_1.entradasDaVarredura)(texto).filter((e) => (0, doctor_pulse_cron_1.entradaDoProjeto)(e, raiz)).map((e) => e.linha);
|
|
178
|
+
return { gerenciadas: doProjeto.filter(linhaGerenciada), manuais: doProjeto.filter((l) => !linhaGerenciada(l)) };
|
|
179
|
+
}
|
|
180
|
+
function ritmoDa(linha) {
|
|
181
|
+
const t = linha.trim();
|
|
182
|
+
const m = t.startsWith('@') ? /^(@\S+)\s/.exec(t) : /^(\S+\s+\S+\s+\S+\s+\S+\s+\S+)\s/.exec(t);
|
|
183
|
+
return m ? (0, doctor_pulse_cron_1.intervaloEmMinutos)(m[1]) : null;
|
|
184
|
+
}
|
|
185
|
+
/** `ork fabrica batida status`: o que o crontab desta maquina diz da batida deste projeto, e a ultima rodada. */
|
|
186
|
+
function estadoDaBatida(raizDoProjeto, opcoes = {}) {
|
|
187
|
+
const raiz = (0, estado_thread_1.raizDoEstado)(raizDoProjeto);
|
|
188
|
+
const executor = opcoes.executor ?? exports.CRONTAB_DO_SISTEMA;
|
|
189
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
190
|
+
const leitura = executor.ler();
|
|
191
|
+
const { gerenciadas, manuais } = leitura.ok ? separar(leitura.texto, raiz) : { gerenciadas: [], manuais: [] };
|
|
192
|
+
const ritmos = [...gerenciadas, ...manuais].map(ritmoDa).filter((r) => r !== null);
|
|
193
|
+
const rodada = (0, doctor_pulse_cron_1.ultimaRodadaDaVarredura)(path.join(raiz, '.orkastery', 'monitor'));
|
|
194
|
+
return {
|
|
195
|
+
projeto: raiz, maquina: (0, maquina_1.nomeDaMaquina)(),
|
|
196
|
+
leitura: leitura.ok ? { ok: true } : { ok: false, motivo: leitura.motivo },
|
|
197
|
+
gerenciada: gerenciadas[0]?.trim() ?? null, manuais: manuais.map((l) => l.trim()),
|
|
198
|
+
instalada: gerenciadas.length + manuais.length > 0,
|
|
199
|
+
intervaloMin: ritmos.length ? Math.min(...ritmos) : null,
|
|
200
|
+
ultimaRodada: rodada ? { ...rodada, haMin: Math.max(0, Math.floor((Date.parse(quando) - Date.parse(rodada.em)) / 60000)) } : null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function lerParaEditar(executor) {
|
|
204
|
+
const leitura = executor.ler();
|
|
205
|
+
// Nunca gravar sem ter lido: um crontab que nao foi lido seria apagado pela gravacao.
|
|
206
|
+
if (!leitura.ok)
|
|
207
|
+
throw new Error(`fabrica.batida.sem-leitura: ${leitura.motivo}; nada foi gravado`);
|
|
208
|
+
return leitura.texto;
|
|
209
|
+
}
|
|
210
|
+
function gravarEConferir(executor, lido, texto, confere) {
|
|
211
|
+
// O crontab que mudou entre a leitura e a gravacao (um `crontab -e`, outro `instalar`) nao e sobrescrito.
|
|
212
|
+
const antes = executor.ler();
|
|
213
|
+
if (!antes.ok || antes.texto !== lido) {
|
|
214
|
+
throw new Error('fabrica.batida.concorrencia: o crontab mudou durante a operacao; nada foi gravado, rode de novo');
|
|
215
|
+
}
|
|
216
|
+
const gravacao = executor.gravar(texto);
|
|
217
|
+
if (!gravacao.ok)
|
|
218
|
+
throw new Error(`fabrica.batida.sem-gravacao: ${gravacao.motivo}`);
|
|
219
|
+
const relido = executor.ler();
|
|
220
|
+
if (!relido.ok || !confere(relido.texto)) {
|
|
221
|
+
throw new Error(`fabrica.batida.nao-confirmada: o crontab relido nao traz o que foi gravado` +
|
|
222
|
+
(relido.ok ? '' : ` (${relido.motivo})`) + '; confira com crontab -l');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** O texto do crontab sem as linhas pedidas, com o fim de linha que o cron exige. */
|
|
226
|
+
function semAsLinhas(texto, sair) {
|
|
227
|
+
const ficam = texto.split('\n').filter((l) => !sair.has(l));
|
|
228
|
+
while (ficam.length && ficam[ficam.length - 1].trim() === '')
|
|
229
|
+
ficam.pop();
|
|
230
|
+
return ficam.length ? `${ficam.join('\n')}\n` : '';
|
|
231
|
+
}
|
|
232
|
+
function noLedgerDaMaquina(tipo, dados) {
|
|
233
|
+
return (0, ledger_1.registrar)((0, maquina_1.pastaDoUsuario)(), 'maquina', tipo, { maquina: (0, maquina_1.nomeDaMaquina)(), ...dados }).eventId;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* `ork fabrica batida instalar`: idempotente. A linha igual ja instalada nao muda nada; a linha antiga do
|
|
237
|
+
* `ork` e a linha posta a mao da varredura deste projeto saem, e a linha do template entra no lugar. As
|
|
238
|
+
* outras entradas do crontab ficam como estavam.
|
|
239
|
+
*/
|
|
240
|
+
function instalarBatida(raizDoProjeto, opcoes = {}) {
|
|
241
|
+
const raiz = (0, estado_thread_1.raizDoEstado)(raizDoProjeto);
|
|
242
|
+
const executor = opcoes.executor ?? exports.CRONTAB_DO_SISTEMA;
|
|
243
|
+
const linha = linhaDaBatida(raiz, opcoes);
|
|
244
|
+
const texto = lerParaEditar(executor);
|
|
245
|
+
const { gerenciadas, manuais } = separar(texto, raiz);
|
|
246
|
+
if (manuais.length === 0 && gerenciadas.length === 1 && gerenciadas[0].trim() === linha) {
|
|
247
|
+
return { acao: 'ja-instalada', projeto: raiz, linha, retiradas: [], manuais: [], eventId: null };
|
|
248
|
+
}
|
|
249
|
+
const retiradas = [...gerenciadas, ...manuais];
|
|
250
|
+
const base = semAsLinhas(texto, new Set(retiradas));
|
|
251
|
+
gravarEConferir(executor, texto, `${base}${linha}\n`, (relido) => relido.split('\n').some((l) => l.trim() === linha));
|
|
252
|
+
const eventId = noLedgerDaMaquina(ledger_1.TIPOS_DE_EVENTO.batidaInstalada, {
|
|
253
|
+
projeto: raiz, linha, retiradas: retiradas.map((l) => l.trim()), por: opcoes.por ?? null,
|
|
254
|
+
evidencia: 'crontab -l relido depois da gravacao traz a linha',
|
|
255
|
+
});
|
|
256
|
+
return { acao: 'instalou', projeto: raiz, linha, retiradas: retiradas.map((l) => l.trim()), manuais: [], eventId };
|
|
257
|
+
}
|
|
258
|
+
/** `ork fabrica batida remover`: tira so a linha que o `ork` instalou para este projeto. */
|
|
259
|
+
function removerBatida(raizDoProjeto, opcoes = {}) {
|
|
260
|
+
const raiz = (0, estado_thread_1.raizDoEstado)(raizDoProjeto);
|
|
261
|
+
const executor = opcoes.executor ?? exports.CRONTAB_DO_SISTEMA;
|
|
262
|
+
const texto = lerParaEditar(executor);
|
|
263
|
+
const { gerenciadas, manuais } = separar(texto, raiz);
|
|
264
|
+
const ficam = manuais.map((l) => l.trim());
|
|
265
|
+
if (gerenciadas.length === 0)
|
|
266
|
+
return { acao: 'nao-instalada', projeto: raiz, linha: null, retiradas: [], manuais: ficam, eventId: null };
|
|
267
|
+
const sair = new Set(gerenciadas);
|
|
268
|
+
gravarEConferir(executor, texto, semAsLinhas(texto, sair), (relido) => separar(relido, raiz).gerenciadas.length === 0);
|
|
269
|
+
const eventId = noLedgerDaMaquina(ledger_1.TIPOS_DE_EVENTO.batidaRemovida, {
|
|
270
|
+
projeto: raiz, retiradas: gerenciadas.map((l) => l.trim()), manuaisQueFicaram: ficam, por: opcoes.por ?? null,
|
|
271
|
+
evidencia: 'crontab -l relido depois da gravacao nao traz a linha do ork',
|
|
272
|
+
});
|
|
273
|
+
return { acao: 'removeu', projeto: raiz, linha: null, retiradas: gerenciadas.map((l) => l.trim()), manuais: ficam, eventId };
|
|
274
|
+
}
|
|
275
|
+
function textoDoEstadoDaBatida(e, quando = new Date().toISOString()) {
|
|
276
|
+
const linhas = [`Batida da fábrica em ${e.maquina}, projeto ${e.projeto}`];
|
|
277
|
+
if (!e.leitura.ok)
|
|
278
|
+
linhas.push(` crontab: não lido (${e.leitura.motivo}); não dá para dizer se a batida está instalada`);
|
|
279
|
+
else if (!e.instalada)
|
|
280
|
+
linhas.push(' instalada: não; instale com: ork fabrica batida instalar');
|
|
281
|
+
else {
|
|
282
|
+
const ritmo = e.intervaloMin === null ? 'com horário que o ork não lê' : `a cada ${e.intervaloMin} min`;
|
|
283
|
+
linhas.push(` instalada: sim, ${ritmo}${e.gerenciada ? ', pelo ork fabrica batida instalar' : ', por linha posta à mão'}`);
|
|
284
|
+
for (const l of [...(e.gerenciada ? [e.gerenciada] : []), ...e.manuais])
|
|
285
|
+
linhas.push(` linha: ${l}`);
|
|
286
|
+
}
|
|
287
|
+
const r = e.ultimaRodada;
|
|
288
|
+
if (!r)
|
|
289
|
+
linhas.push(' última rodada: nenhuma registrada (.orkastery/monitor/hitl.log)');
|
|
290
|
+
else {
|
|
291
|
+
linhas.push(` última rodada: ${(0, horario_1.formatarHora)(r.em, { agora: quando })} (há ${r.haMin} min), ` +
|
|
292
|
+
`${r.code === 0 ? 'ok' : `código ${r.code ?? '?'}`}: ${r.detalhe || 'sem detalhe'}`);
|
|
293
|
+
if (e.instalada && r.haMin > doctor_pulse_cron_1.ATRASO_DA_BATIDA_MIN) {
|
|
294
|
+
linhas.push(` atrasada: nenhuma rodada há ${r.haMin} min com a batida instalada; confira crontab -l e o hitl.log`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
linhas.push((0, horario_1.legendaDoFuso)());
|
|
298
|
+
return linhas.join('\n');
|
|
299
|
+
}
|
|
300
|
+
function textoDoResultadoDaBatida(r) {
|
|
301
|
+
switch (r.acao) {
|
|
302
|
+
case 'ja-instalada': return `Batida já instalada para ${r.projeto}; nada mudou no crontab.\n linha: ${r.linha}`;
|
|
303
|
+
case 'instalou': return [`Batida instalada para ${r.projeto} (evento ${r.eventId} no ledger da máquina).`, ` linha: ${r.linha}`,
|
|
304
|
+
...r.retiradas.map((l) => ` saiu: ${l}`), ' confira com: ork fabrica batida status'].join('\n');
|
|
305
|
+
case 'removeu': return [`Batida removida de ${r.projeto} (evento ${r.eventId} no ledger da máquina).`,
|
|
306
|
+
...r.retiradas.map((l) => ` saiu: ${l}`), ...r.manuais.map((l) => ` ficou (posta à mão, não é do ork): ${l}`)].join('\n');
|
|
307
|
+
case 'nao-instalada': return [`Nenhuma batida do ork instalada para ${r.projeto}; nada mudou no crontab.`,
|
|
308
|
+
...r.manuais.map((l) => ` ficou (posta à mão, não é do ork): ${l}`)].join('\n');
|
|
309
|
+
}
|
|
310
|
+
}
|