@orkastery/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +163 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-objective-message.sh +17 -0
- package/adapters/hermes/bin/ork-objective-status.sh +12 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +26 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +135 -26
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +458 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +11 -8
- package/dist/auditrun.js +5 -4
- package/dist/board.js +173 -22
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +216 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +183 -24
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +21 -0
- package/dist/gates.js +55 -9
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1736 -93
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +107 -2
- package/dist/licoes.js +215 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +596 -150
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +637 -115
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +683 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +150 -15
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +2 -0
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +35 -1
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +2 -2
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +25 -4
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
package/dist/ocupacao.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Politica de OCUPACAO DE VAGA por estado real (correcao do escalonador, 09/2026).
|
|
4
|
+
*
|
|
5
|
+
* O incidente que este arquivo corrige: o escalonador contava "em andamento" pela
|
|
6
|
+
* EXISTENCIA da thread aberta, e nao por haver um agente executando de fato. Threads
|
|
7
|
+
* orfas presas ha dias em gates nao-finalizaveis seguravam as vagas de
|
|
8
|
+
* `concurrency.max_parallel_threads` para sempre (em producao: em_andamento=0 e 15
|
|
9
|
+
* threads em `concurrency.limite`, porque 3 orfas ocupavam as 3 vagas sem rodar nada).
|
|
10
|
+
*
|
|
11
|
+
* A regra que mora aqui, e SO aqui (domicilio unico, consumido por `board.ts` e
|
|
12
|
+
* `orquestracao.ts`): uma thread tem PROCURA ATIVA quando a sessao do seu ultimo
|
|
13
|
+
* despacho esta `working` no runtime, ou quando houve atividade recente no ledger de
|
|
14
|
+
* uma thread ja despachada (fase em execucao entre eventos). Pausa humana aberta,
|
|
15
|
+
* impedimento nao-transitorio aberto e stale (sem rodar ha mais que
|
|
16
|
+
* `concurrency.stale_after_min`) NAO ocupam vaga: a vaga volta para quem esta pronto.
|
|
17
|
+
*
|
|
18
|
+
* Tudo e DERIVADO de `thread.json`, `ledger.jsonl` e da lista de sessoes do runtime:
|
|
19
|
+
* avaliar ocupacao nao grava nada, e por isso e deterministico e seguro de rodar em
|
|
20
|
+
* laco. O registro verificavel da devolucao de vaga e o `ork board reap`, que grava
|
|
21
|
+
* `slot_released` no ledger da thread devolvida.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.EVENTOS_QUE_DESTRAVAM = exports.PAUSA_PREVISTA = void 0;
|
|
25
|
+
exports.ehAprovacaoHumana = ehAprovacaoHumana;
|
|
26
|
+
exports.ehPausaPrevista = ehPausaPrevista;
|
|
27
|
+
exports.faseTemPausaPrevista = faseTemPausaPrevista;
|
|
28
|
+
exports.avaliarOcupacao = avaliarOcupacao;
|
|
29
|
+
const ledger_1 = require("./ledger");
|
|
30
|
+
const sessoes_adopt_1 = require("./sessoes-adopt");
|
|
31
|
+
/** Estado que o `ork phase run` grava quando o bloco despachado pausa ao fim. */
|
|
32
|
+
exports.PAUSA_PREVISTA = 'prevista ao fim do bloco';
|
|
33
|
+
/**
|
|
34
|
+
* Eventos que destravam uma reprovacao de gate.
|
|
35
|
+
*
|
|
36
|
+
* `phase_dispatch` entra pela porta da frente: o `ork phase run` grava `gate_blocked`
|
|
37
|
+
* ANTES de despachar e aborta o despacho, entao um `phase_dispatch` posterior so existe
|
|
38
|
+
* se aquele bloqueio deixou de valer.
|
|
39
|
+
*/
|
|
40
|
+
exports.EVENTOS_QUE_DESTRAVAM = [
|
|
41
|
+
ledger_1.TIPOS_DE_EVENTO.gateLiberado,
|
|
42
|
+
ledger_1.TIPOS_DE_EVENTO.faseDespachada,
|
|
43
|
+
ledger_1.TIPOS_DE_EVENTO.leaseAdquirido,
|
|
44
|
+
ledger_1.TIPOS_DE_EVENTO.rateLimitRetomado,
|
|
45
|
+
ledger_1.TIPOS_DE_EVENTO.shipConcluido,
|
|
46
|
+
ledger_1.TIPOS_DE_EVENTO.masterConcluido,
|
|
47
|
+
];
|
|
48
|
+
/** O evento e uma autorizacao humana ja registrada (`ork gate approve`)? */
|
|
49
|
+
function ehAprovacaoHumana(e) {
|
|
50
|
+
return e.tipo === ledger_1.TIPOS_DE_EVENTO.pausaHumana && e.estado === 'aprovado';
|
|
51
|
+
}
|
|
52
|
+
/** O evento e a pausa PREVISTA gravada no despacho do ultimo bloco? */
|
|
53
|
+
function ehPausaPrevista(e) {
|
|
54
|
+
return e.tipo === ledger_1.TIPOS_DE_EVENTO.pausaHumana && e.estado === exports.PAUSA_PREVISTA;
|
|
55
|
+
}
|
|
56
|
+
/** Auto depende do bloco; demais modos conservam o fallback durável após avanço de fase. */
|
|
57
|
+
function faseTemPausaPrevista(thread) {
|
|
58
|
+
return thread.modo !== 'auto' || thread.blocos.some(b => b.pausa && b.fases.includes(thread.faseAtual));
|
|
59
|
+
}
|
|
60
|
+
/** sessionId do ultimo despacho: o ledger primeiro, o `thread.json` como reserva. */
|
|
61
|
+
function ultimaSessao(thread, eventos) {
|
|
62
|
+
for (let i = eventos.length - 1; i >= 0; i--) {
|
|
63
|
+
const s = eventos[i].sessionId;
|
|
64
|
+
if (typeof s === 'string' && s !== '')
|
|
65
|
+
return s;
|
|
66
|
+
}
|
|
67
|
+
const registrada = thread.sessoes[thread.sessoes.length - 1];
|
|
68
|
+
return registrada && registrada.sessionId ? registrada.sessionId : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Carimbo do ultimo sinal de vida: o evento mais novo do ledger que seja TRABALHO da
|
|
72
|
+
* thread. `slot_released` fica de fora: e contabilidade do escalonador, e se contasse
|
|
73
|
+
* como atividade a thread stale rejuvenesceria no exato momento em que e expulsa.
|
|
74
|
+
*/
|
|
75
|
+
function ultimaAtividadeEm(eventos) {
|
|
76
|
+
for (let i = eventos.length - 1; i >= 0; i--) {
|
|
77
|
+
if (eventos[i].tipo !== ledger_1.TIPOS_DE_EVENTO.vagaLiberada)
|
|
78
|
+
return eventos[i].ts;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/** A thread tem pausa humana ABERTA? Mesmas tres fontes do monitor, resumidas. */
|
|
83
|
+
function pausaHumanaAberta(thread, eventos) {
|
|
84
|
+
if (thread.status === 'pausada' && faseTemPausaPrevista(thread)) {
|
|
85
|
+
return { desdeEm: thread.atualizadaEm, evidencia: 'thread.json (status: pausada)' };
|
|
86
|
+
}
|
|
87
|
+
const indiceDaFase = (fase) => typeof fase === 'string' ? thread.fases.indexOf(fase) : -1;
|
|
88
|
+
for (let i = 0; i < eventos.length; i++) {
|
|
89
|
+
const e = eventos[i];
|
|
90
|
+
const prevista = ehPausaPrevista(e);
|
|
91
|
+
const escalada = e.tipo === ledger_1.TIPOS_DE_EVENTO.gateBloqueado && e.motivo === 'human.pending';
|
|
92
|
+
if (!prevista && !escalada)
|
|
93
|
+
continue;
|
|
94
|
+
const iFase = indiceDaFase(e.fase);
|
|
95
|
+
// Na pausa prevista, redespachar a MESMA fase nao resolve pausa nenhuma: so o
|
|
96
|
+
// avanco para fase posterior, a aprovacao humana ou o MASTER concluido resolvem.
|
|
97
|
+
const resolvida = eventos.slice(i + 1).some((p) => {
|
|
98
|
+
if (ehAprovacaoHumana(p))
|
|
99
|
+
return true;
|
|
100
|
+
if (p.tipo === ledger_1.TIPOS_DE_EVENTO.masterConcluido)
|
|
101
|
+
return true;
|
|
102
|
+
if (escalada && exports.EVENTOS_QUE_DESTRAVAM.includes(p.tipo))
|
|
103
|
+
return true;
|
|
104
|
+
return (prevista && p.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada && indiceDaFase(p.fase) > iFase);
|
|
105
|
+
});
|
|
106
|
+
if (resolvida)
|
|
107
|
+
continue;
|
|
108
|
+
return {
|
|
109
|
+
desdeEm: e.ts,
|
|
110
|
+
evidencia: prevista
|
|
111
|
+
? `ledger ${ledger_1.TIPOS_DE_EVENTO.pausaHumana} (${exports.PAUSA_PREVISTA}) em ${e.ts}`
|
|
112
|
+
: `ledger ${ledger_1.TIPOS_DE_EVENTO.gateBloqueado} (human.pending) em ${e.ts}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/** Gate tipado reprovado sem destravamento: o impedimento nao-transitorio aberto. */
|
|
118
|
+
function impedimentoAberto(eventos) {
|
|
119
|
+
for (let i = eventos.length - 1; i >= 0; i--) {
|
|
120
|
+
const e = eventos[i];
|
|
121
|
+
if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.gateBloqueado && e.tipo !== 'phase_wait')
|
|
122
|
+
continue;
|
|
123
|
+
if (typeof e.motivo !== 'string')
|
|
124
|
+
continue;
|
|
125
|
+
const motivo = e.motivo;
|
|
126
|
+
// `human.pending` ja e pausa humana; `claims.unverifiable` avisa mas nao bloqueia.
|
|
127
|
+
if (motivo === 'human.pending' || motivo === 'claims.unverifiable')
|
|
128
|
+
continue;
|
|
129
|
+
const resolvido = eventos
|
|
130
|
+
.slice(i + 1)
|
|
131
|
+
.some((p) => exports.EVENTOS_QUE_DESTRAVAM.includes(p.tipo) || ehAprovacaoHumana(p));
|
|
132
|
+
if (resolvido)
|
|
133
|
+
continue;
|
|
134
|
+
return {
|
|
135
|
+
motivo,
|
|
136
|
+
desdeEm: e.ts,
|
|
137
|
+
evidencia: `ledger ${e.tipo} (${motivo}) em ${e.ts}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/** Minutos inteiros entre dois carimbos ISO (nunca negativo). */
|
|
143
|
+
function minutos(desdeEm, ateEm) {
|
|
144
|
+
const inicio = Date.parse(desdeEm);
|
|
145
|
+
const fim = Date.parse(ateEm);
|
|
146
|
+
if (!Number.isFinite(inicio) || !Number.isFinite(fim))
|
|
147
|
+
return 0;
|
|
148
|
+
return Math.max(0, Math.floor((fim - inicio) / 60000));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Avalia a PROCURA ATIVA de uma thread, na ordem que decide a vaga:
|
|
152
|
+
*
|
|
153
|
+
* 1. sessao `working` no runtime ocupa vaga, mesmo com pausa prevista no ledger --
|
|
154
|
+
* a pausa so chega quando a sessao terminar (licao do incidente de 05/09/2026);
|
|
155
|
+
* 2. sessao `blocked` e pausa humana JA chegada (HITL no runtime): nao ocupa;
|
|
156
|
+
* 3. pausa humana aberta nao ocupa: esperar humano nao e executar;
|
|
157
|
+
* 4. impedimento nao-transitorio aberto nao ocupa;
|
|
158
|
+
* 5. despachada e parada ha mais que `staleMin` vira `stale` e devolve a vaga;
|
|
159
|
+
* 6. o resto esta `ociosa`: pronta (inclusive entre fases, com a sessao do bloco
|
|
160
|
+
* anterior terminada ou o runtime nao consultado), e entra por FIFO quando
|
|
161
|
+
* houver vaga -- e ai conta contra o limite, como sempre contou.
|
|
162
|
+
*/
|
|
163
|
+
function avaliarOcupacao(thread, eventos, opcoes) {
|
|
164
|
+
const sessionId = ultimaSessao(thread, eventos);
|
|
165
|
+
const sessaoEstado = opcoes.estados === null || sessionId === null
|
|
166
|
+
? null
|
|
167
|
+
: opcoes.estados.get(sessionId) ?? null;
|
|
168
|
+
const base = { sessionId, sessaoEstado };
|
|
169
|
+
if (thread.status === 'fechada') {
|
|
170
|
+
return {
|
|
171
|
+
...base,
|
|
172
|
+
classe: 'ociosa',
|
|
173
|
+
ocupaVaga: false,
|
|
174
|
+
motivo: null,
|
|
175
|
+
detalhe: 'thread fechada nao disputa vaga',
|
|
176
|
+
evidencia: 'thread.json (status: fechada)',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const registro = (0, sessoes_adopt_1.ehRegistroDeAdocao)(thread);
|
|
180
|
+
if (!registro && (sessaoEstado === 'working' || (sessaoEstado === '' && sessionId !== null))) {
|
|
181
|
+
return {
|
|
182
|
+
...base,
|
|
183
|
+
classe: 'ativa',
|
|
184
|
+
ocupaVaga: true,
|
|
185
|
+
motivo: null,
|
|
186
|
+
detalhe: `sessao ${String(sessionId).slice(0, 8)} trabalhando no runtime`,
|
|
187
|
+
evidencia: `claude agents --json (sessao ${String(sessionId).slice(0, 8)} state=working)`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (sessaoEstado === 'blocked') {
|
|
191
|
+
return {
|
|
192
|
+
...base,
|
|
193
|
+
classe: 'pausa-humana',
|
|
194
|
+
ocupaVaga: false,
|
|
195
|
+
motivo: 'human.pending',
|
|
196
|
+
detalhe: `sessao ${String(sessionId).slice(0, 8)} BLOQUEADA no runtime esperando acao humana: ` +
|
|
197
|
+
'esperar humano nao ocupa vaga da maquina',
|
|
198
|
+
evidencia: `claude agents --json (sessao ${String(sessionId).slice(0, 8)} state=blocked)`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const pausa = pausaHumanaAberta(thread, eventos);
|
|
202
|
+
if (pausa) {
|
|
203
|
+
return {
|
|
204
|
+
...base,
|
|
205
|
+
classe: 'pausa-humana',
|
|
206
|
+
ocupaVaga: false,
|
|
207
|
+
motivo: 'human.pending',
|
|
208
|
+
detalhe: `pausa humana aberta ha ${minutos(pausa.desdeEm, opcoes.agora)} min: ` +
|
|
209
|
+
'esperar veredito humano nao ocupa vaga da maquina',
|
|
210
|
+
evidencia: pausa.evidencia,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (registro)
|
|
214
|
+
return { ...base, classe: 'ociosa', ocupaVaga: false, motivo: null,
|
|
215
|
+
detalhe: 'registro de adoção não disputa vaga de trabalho',
|
|
216
|
+
evidencia: 'thread.json: origem de adoção, sem sessão despachada' };
|
|
217
|
+
const impedimento = impedimentoAberto(eventos);
|
|
218
|
+
if (impedimento) {
|
|
219
|
+
return {
|
|
220
|
+
...base,
|
|
221
|
+
classe: 'impedida',
|
|
222
|
+
ocupaVaga: false,
|
|
223
|
+
motivo: impedimento.motivo,
|
|
224
|
+
detalhe: `gate ${impedimento.motivo} aberto ha ${minutos(impedimento.desdeEm, opcoes.agora)} min ` +
|
|
225
|
+
'sem evento que destrave: impedimento nao-transitorio nao ocupa vaga',
|
|
226
|
+
evidencia: impedimento.evidencia,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const foiDespachada = sessionId !== null;
|
|
230
|
+
const atividadeEm = ultimaAtividadeEm(eventos);
|
|
231
|
+
if (foiDespachada && atividadeEm !== null) {
|
|
232
|
+
const paradaHaMin = minutos(atividadeEm, opcoes.agora);
|
|
233
|
+
if (paradaHaMin >= opcoes.staleMin) {
|
|
234
|
+
return {
|
|
235
|
+
...base,
|
|
236
|
+
classe: 'stale',
|
|
237
|
+
ocupaVaga: false,
|
|
238
|
+
motivo: 'vaga.stale',
|
|
239
|
+
detalhe: `despachada e sem atividade ha ${paradaHaMin} min (limite ${opcoes.staleMin} min, ` +
|
|
240
|
+
'concurrency.stale_after_min) com sessao nao-working: a vaga foi devolvida',
|
|
241
|
+
evidencia: `ledger parado desde ${atividadeEm}` +
|
|
242
|
+
(sessaoEstado !== null
|
|
243
|
+
? `; sessao ${String(sessionId).slice(0, 8)} state=${sessaoEstado}`
|
|
244
|
+
: opcoes.estados !== null
|
|
245
|
+
? `; sessao ${String(sessionId).slice(0, 8)} fora da lista do runtime`
|
|
246
|
+
: '; runtime nao consultado'),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// Sessao nao-working (ou runtime nao consultado) com atividade recente: a thread
|
|
250
|
+
// esta entre fases, pronta para o proximo despacho. Ela disputa vaga por FIFO,
|
|
251
|
+
// nao por posse -- e continua contando contra o limite quando recebe a vaga.
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
...base,
|
|
255
|
+
classe: 'ociosa',
|
|
256
|
+
ocupaVaga: false,
|
|
257
|
+
motivo: null,
|
|
258
|
+
detalhe: foiDespachada
|
|
259
|
+
? 'pronta para o proximo despacho: a sessao do bloco anterior terminou'
|
|
260
|
+
: 'pronta para rodar: sem despacho ativo, sem pausa e sem impedimento',
|
|
261
|
+
evidencia: !foiDespachada
|
|
262
|
+
? 'thread.json + ledger sem despacho em aberto'
|
|
263
|
+
: opcoes.estados !== null
|
|
264
|
+
? `sessao ${String(sessionId).slice(0, 8)} nao-working na lista do runtime`
|
|
265
|
+
: `sessao ${String(sessionId).slice(0, 8)} com runtime nao consultado`,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
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.ETAPAS_ONBOARDING = exports.PAUTA_ONBOARDING = exports.CONTRATO_ONBOARDING = void 0;
|
|
37
|
+
exports.caminhoOnboarding = caminhoOnboarding;
|
|
38
|
+
exports.validarConteudo = validarConteudo;
|
|
39
|
+
exports.jsonCanonico = jsonCanonico;
|
|
40
|
+
exports.onboardingPadrao = onboardingPadrao;
|
|
41
|
+
exports.lerOnboarding = lerOnboarding;
|
|
42
|
+
exports.gravarEtapa = gravarEtapa;
|
|
43
|
+
exports.resetarOnboarding = resetarOnboarding;
|
|
44
|
+
exports.textoDaPauta = textoDaPauta;
|
|
45
|
+
/** Entrevista determinística; hosts conduzem a conversa a partir desta pauta. */
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
|
+
const manifest_1 = require("./manifest");
|
|
50
|
+
const project_state_1 = require("./project-state");
|
|
51
|
+
const ledger_1 = require("./ledger");
|
|
52
|
+
const policies_1 = require("./policies");
|
|
53
|
+
const horario_1 = require("./horario");
|
|
54
|
+
exports.CONTRATO_ONBOARDING = 'ork.onboarding/v1';
|
|
55
|
+
exports.PAUTA_ONBOARDING = [
|
|
56
|
+
{ etapa: 'maestro', pergunta: `Quem conduz o projeto, quais são seus objetivos e preferências, e em qual fuso horário o Orkastery deve mostrar horários? Informe o fuso como nome IANA, ex.: {"fuso":"${horario_1.FUSO_DE_BRASILIA}"}; ele orienta ${horario_1.CHAVE_DO_FUSO} no manifesto, sem editá-lo.` },
|
|
57
|
+
{ etapa: 'credenciais', pergunta: 'Quais provedores públicos e nomes de variáveis em env serão usados? Segredos somente em ~/.hermes/.env; nunca informe valores.' },
|
|
58
|
+
{ etapa: 'bancos', pergunta: 'Quais bancos públicos e nomes de variáveis em env configuram as conexões? Nunca informe DSN ou senha; valores somente em ~/.hermes/.env.' },
|
|
59
|
+
{ etapa: 'memoria', pergunta: 'Deseja OrkMind? Informe {"modo":"orkmind"} ou {"modo":"files"}; a escolha orienta memory.mode no manifesto, sem editá-lo.' },
|
|
60
|
+
{ etapa: 'produtos', pergunta: 'Quais produtos e repositórios públicos pertencem ao projeto?' },
|
|
61
|
+
{ etapa: 'topologia', pergunta: 'Como se distribuem ambientes e serviços? Informe identificadores públicos.' },
|
|
62
|
+
{ etapa: 'arquitetura', pergunta: 'Quais decisões e restrições de arquitetura devem orientar o trabalho?' },
|
|
63
|
+
{ etapa: 'skills', pergunta: 'Quais capacidades e skills serão necessárias?' },
|
|
64
|
+
{ etapa: 'auditores', pergunta: 'Quais auditorias e cadências são desejadas?' },
|
|
65
|
+
];
|
|
66
|
+
exports.ETAPAS_ONBOARDING = exports.PAUTA_ONBOARDING.map(p => p.etapa);
|
|
67
|
+
function caminhoOnboarding(raiz) {
|
|
68
|
+
return (0, project_state_1.stateFile)(raiz, 'onboarding.json');
|
|
69
|
+
}
|
|
70
|
+
function objeto(v) {
|
|
71
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v) &&
|
|
72
|
+
[Object.prototype, null].includes(Object.getPrototypeOf(v));
|
|
73
|
+
}
|
|
74
|
+
/** Recusa antes de persistir. Diagnósticos nunca incluem a entrada recebida. */
|
|
75
|
+
function invalido() { throw new Error('onboarding.input.invalid: use JSON público e referências de variáveis; segredos somente em ~/.hermes/.env'); }
|
|
76
|
+
function textoSeguro(v) {
|
|
77
|
+
return !(0, policies_1.procurarSegredos)(v).length &&
|
|
78
|
+
!/[a-z][a-z0-9+.-]*:\/\/[^\s/]*@/i.test(v) &&
|
|
79
|
+
!/\b(?:senha|password|secret|token|api[_-]?key|authorization)\s*[:=]\s*\S+/i.test(v) &&
|
|
80
|
+
!/\bBearer\s+\S+|-----BEGIN|\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\./.test(v);
|
|
81
|
+
}
|
|
82
|
+
function validarJson(v, profundidade = 0) {
|
|
83
|
+
if (profundidade > 12)
|
|
84
|
+
invalido();
|
|
85
|
+
if (v === null || typeof v === 'boolean')
|
|
86
|
+
return;
|
|
87
|
+
if (typeof v === 'number') {
|
|
88
|
+
if (!Number.isFinite(v))
|
|
89
|
+
invalido();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (typeof v === 'string') {
|
|
93
|
+
if (!textoSeguro(v))
|
|
94
|
+
invalido();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(v)) {
|
|
98
|
+
for (const item of v)
|
|
99
|
+
validarJson(item, profundidade + 1);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!objeto(v))
|
|
103
|
+
invalido();
|
|
104
|
+
for (const [k, item] of Object.entries(v)) {
|
|
105
|
+
if (['__proto__', 'constructor', 'prototype'].includes(k) || !textoSeguro(k))
|
|
106
|
+
invalido();
|
|
107
|
+
if (/(?:senha|password|secret|token|api[_-]?key|authorization|credential|credencial|dsn|private[_-]?key)/i.test(k)) {
|
|
108
|
+
if (!/_env$/.test(k) || typeof item !== 'string' || !/^[A-Z][A-Z0-9_]{1,127}$/.test(item))
|
|
109
|
+
invalido();
|
|
110
|
+
}
|
|
111
|
+
validarJson(item, profundidade + 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function validarConteudo(etapa, conteudo) {
|
|
115
|
+
validarJson(conteudo);
|
|
116
|
+
if (Buffer.byteLength(JSON.stringify(conteudo)) > 16384)
|
|
117
|
+
invalido();
|
|
118
|
+
// I-35: o fuso do dono, quando informado, precisa ser um nome IANA que o Intl aceita.
|
|
119
|
+
if (etapa === 'maestro' && objeto(conteudo) && Object.hasOwn(conteudo, 'fuso') && (0, horario_1.normalizarFuso)(conteudo.fuso) === undefined)
|
|
120
|
+
invalido();
|
|
121
|
+
// Etapas sensíveis usam um formato fechado: não há espaço para texto de credenciais.
|
|
122
|
+
if (etapa === 'credenciais' || etapa === 'bancos') {
|
|
123
|
+
if (!objeto(conteudo))
|
|
124
|
+
invalido();
|
|
125
|
+
for (const [k, v] of Object.entries(conteudo)) {
|
|
126
|
+
if (k === 'env') {
|
|
127
|
+
if (!Array.isArray(v) || v.some(x => typeof x !== 'string' || !/^[A-Z][A-Z0-9_]{1,127}$/.test(x)))
|
|
128
|
+
invalido();
|
|
129
|
+
}
|
|
130
|
+
else if (k === 'provedor' || k === 'banco') {
|
|
131
|
+
if (typeof v !== 'string' || !/^[a-zA-Z][a-zA-Z0-9._ -]{0,79}$/.test(v))
|
|
132
|
+
invalido();
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
invalido();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Chaves ordenadas recursivamente; a ordem de arrays continua significativa. */
|
|
140
|
+
function jsonCanonico(v) {
|
|
141
|
+
if (Array.isArray(v))
|
|
142
|
+
return '[' + v.map(jsonCanonico).join(',') + ']';
|
|
143
|
+
if (objeto(v))
|
|
144
|
+
return '{' + Object.keys(v).sort().map(k => JSON.stringify(k) + ':' + jsonCanonico(v[k])).join(',') + '}';
|
|
145
|
+
return JSON.stringify(v);
|
|
146
|
+
}
|
|
147
|
+
function sha(v) { return (0, node_crypto_1.createHash)('sha256').update(jsonCanonico(v)).digest('hex'); }
|
|
148
|
+
function onboardingPadrao() {
|
|
149
|
+
return { contrato: exports.CONTRATO_ONBOARDING, atualizadoEm: null,
|
|
150
|
+
etapas: Object.fromEntries(exports.ETAPAS_ONBOARDING.map(e => [e, null])) };
|
|
151
|
+
}
|
|
152
|
+
function lerArquivo(raiz) {
|
|
153
|
+
const arquivo = caminhoOnboarding(raiz);
|
|
154
|
+
if (fs.statSync(arquivo).size > 256 * 1024)
|
|
155
|
+
legadoInvalido();
|
|
156
|
+
return JSON.parse(fs.readFileSync(arquivo, 'utf8'));
|
|
157
|
+
}
|
|
158
|
+
function dataMaisRecente(datas) {
|
|
159
|
+
return datas.filter((d) => typeof d === 'string' && Number.isFinite(Date.parse(d)))
|
|
160
|
+
.reduce((maior, d) => maior === null || Date.parse(d) > Date.parse(maior) ? d : maior, null);
|
|
161
|
+
}
|
|
162
|
+
function normalizar(bruto) {
|
|
163
|
+
const estado = onboardingPadrao();
|
|
164
|
+
if (!objeto(bruto) || !objeto(bruto.etapas))
|
|
165
|
+
return estado;
|
|
166
|
+
for (const etapa of exports.ETAPAS_ONBOARDING) {
|
|
167
|
+
const r = bruto.etapas[etapa];
|
|
168
|
+
if (!objeto(r) || typeof r.por !== 'string' || !r.por.trim() || r.por.length > 120 || !textoSeguro(r.por) ||
|
|
169
|
+
typeof r.respondidaEm !== 'string' || !Number.isFinite(Date.parse(r.respondidaEm)))
|
|
170
|
+
continue;
|
|
171
|
+
try {
|
|
172
|
+
validarConteudo(etapa, r.conteudo);
|
|
173
|
+
estado.etapas[etapa] = { por: r.por, respondidaEm: r.respondidaEm, conteudo: r.conteudo };
|
|
174
|
+
}
|
|
175
|
+
catch { /* entrada legada insegura permanece pendente */ }
|
|
176
|
+
}
|
|
177
|
+
const datas = Object.values(estado.etapas).filter((r) => !!r).map(r => r.respondidaEm);
|
|
178
|
+
estado.atualizadoEm = dataMaisRecente([bruto.atualizadoEm, ...datas]);
|
|
179
|
+
return estado;
|
|
180
|
+
}
|
|
181
|
+
function lerOnboarding(raiz) {
|
|
182
|
+
const state = (0, project_state_1.projectState)(raiz);
|
|
183
|
+
try {
|
|
184
|
+
const value = lerArquivo(raiz);
|
|
185
|
+
if (state.linked && (!objeto(value) || !objeto(value.etapas)))
|
|
186
|
+
throw new Error('project-state.source.invalid');
|
|
187
|
+
return normalizar(value);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
if (state.linked)
|
|
191
|
+
throw new Error('project-state.source.unavailable');
|
|
192
|
+
return onboardingPadrao();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function legadoInvalido() {
|
|
196
|
+
throw new Error('onboarding.legacy.invalid: escrita recusada para preservar respostas não representáveis; revise o arquivo ou use reset explícito da etapa.');
|
|
197
|
+
}
|
|
198
|
+
/** Chamada somente sob trava; uma leitura tolerante não autoriza descarte na escrita. */
|
|
199
|
+
function lerParaMutacao(raiz, reset = []) {
|
|
200
|
+
let bruto;
|
|
201
|
+
try {
|
|
202
|
+
bruto = lerArquivo(raiz);
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
if (e.code === 'ENOENT')
|
|
206
|
+
return { estado: onboardingPadrao(), descartadas: [] };
|
|
207
|
+
legadoInvalido();
|
|
208
|
+
}
|
|
209
|
+
if (!objeto(bruto) || !objeto(bruto.etapas))
|
|
210
|
+
legadoInvalido();
|
|
211
|
+
const estado = normalizar(bruto);
|
|
212
|
+
const descartadas = Object.entries(bruto.etapas).filter(([etapa, valor]) => valor !== null &&
|
|
213
|
+
(!exports.ETAPAS_ONBOARDING.includes(etapa) || estado.etapas[etapa] === null)).map(([etapa]) => etapa);
|
|
214
|
+
if (descartadas.some(etapa => !reset.includes(etapa)))
|
|
215
|
+
legadoInvalido();
|
|
216
|
+
return { estado, descartadas };
|
|
217
|
+
}
|
|
218
|
+
function exigirEtapa(etapa) {
|
|
219
|
+
if (!exports.ETAPAS_ONBOARDING.includes(etapa))
|
|
220
|
+
invalido();
|
|
221
|
+
return etapa;
|
|
222
|
+
}
|
|
223
|
+
function exigirAutor(por) {
|
|
224
|
+
if (typeof por !== 'string' || !por.trim() || por.length > 120 || !textoSeguro(por))
|
|
225
|
+
invalido();
|
|
226
|
+
}
|
|
227
|
+
function persistir(raiz, estado) {
|
|
228
|
+
const arquivo = caminhoOnboarding(raiz), temporario = arquivo + '.' + (0, node_crypto_1.randomUUID)() + '.tmp';
|
|
229
|
+
fs.mkdirSync(path.dirname(arquivo), { recursive: true });
|
|
230
|
+
try {
|
|
231
|
+
fs.writeFileSync(temporario, JSON.stringify(estado, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
232
|
+
fs.renameSync(temporario, arquivo);
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
if (fs.existsSync(temporario))
|
|
236
|
+
fs.unlinkSync(temporario);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Serializa toda a mutação. Nunca toma uma trava por idade ou remove trava alheia. */
|
|
240
|
+
function comTrava(raiz, alterar) {
|
|
241
|
+
const trava = caminhoOnboarding(raiz) + '.lock';
|
|
242
|
+
fs.mkdirSync(path.dirname(trava), { recursive: true });
|
|
243
|
+
const limite = Date.now() + 5000;
|
|
244
|
+
let fd;
|
|
245
|
+
for (;;) {
|
|
246
|
+
try {
|
|
247
|
+
fd = fs.openSync(trava, 'wx', 0o600);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
if (e.code !== 'EEXIST')
|
|
252
|
+
throw e;
|
|
253
|
+
if (Date.now() >= limite)
|
|
254
|
+
throw new Error('onboarding.busy: entrevista em uso; tente novamente. Se persistir, confira o encerramento dos escritores antes de remover onboarding.json.lock.');
|
|
255
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
return alterar();
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
fs.closeSync(fd);
|
|
263
|
+
fs.unlinkSync(trava);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function gravarEtapa(raiz, nome, conteudo, por = 'owner') {
|
|
267
|
+
const etapa = exigirEtapa(nome);
|
|
268
|
+
exigirAutor(por);
|
|
269
|
+
validarConteudo(etapa, conteudo);
|
|
270
|
+
return comTrava(raiz, () => {
|
|
271
|
+
const { estado } = lerParaMutacao(raiz), anterior = estado.etapas[etapa];
|
|
272
|
+
if (anterior && jsonCanonico(anterior.conteudo) === jsonCanonico(conteudo))
|
|
273
|
+
return estado;
|
|
274
|
+
const timestamp = new Date().toISOString();
|
|
275
|
+
estado.etapas[etapa] = { respondidaEm: timestamp, por, conteudo };
|
|
276
|
+
estado.atualizadoEm = dataMaisRecente([estado.atualizadoEm, timestamp]);
|
|
277
|
+
persistir(raiz, estado);
|
|
278
|
+
(0, ledger_1.registrar)((0, manifest_1.dirEstado)((0, project_state_1.projectState)(raiz).root), 'projeto', ledger_1.TIPOS_DE_EVENTO.onboardingRegistrado, { acao: 'set', etapa, por, sha256: sha(conteudo) });
|
|
279
|
+
return estado;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function resetarOnboarding(raiz, nome, por = 'owner') {
|
|
283
|
+
exigirAutor(por);
|
|
284
|
+
const etapas = nome === undefined ? exports.ETAPAS_ONBOARDING : [exigirEtapa(nome)];
|
|
285
|
+
return comTrava(raiz, () => {
|
|
286
|
+
const { estado, descartadas } = lerParaMutacao(raiz, etapas);
|
|
287
|
+
const alteradas = etapas.filter(e => estado.etapas[e] !== null || descartadas.includes(e));
|
|
288
|
+
if (!alteradas.length)
|
|
289
|
+
return estado;
|
|
290
|
+
for (const etapa of alteradas)
|
|
291
|
+
estado.etapas[etapa] = null;
|
|
292
|
+
estado.atualizadoEm = dataMaisRecente([estado.atualizadoEm, new Date().toISOString()]);
|
|
293
|
+
persistir(raiz, estado);
|
|
294
|
+
for (const etapa of alteradas)
|
|
295
|
+
(0, ledger_1.registrar)((0, manifest_1.dirEstado)((0, project_state_1.projectState)(raiz).root), 'projeto', ledger_1.TIPOS_DE_EVENTO.onboardingRegistrado, { acao: 'reset', etapa, por, sha256: sha(null) });
|
|
296
|
+
return estado;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function textoDaPauta(estado = onboardingPadrao()) {
|
|
300
|
+
return ['Onboarding do projeto (' + exports.CONTRATO_ONBOARDING + ')',
|
|
301
|
+
...exports.PAUTA_ONBOARDING.map(({ etapa, pergunta }) => `${estado.etapas[etapa] ? '[respondida]' : '[pendente]'} ${etapa}: ${pergunta}`),
|
|
302
|
+
'Responda com ork onboarding set <etapa> --conteudo <JSON> --por <quem>.'].join('\n');
|
|
303
|
+
}
|