@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.
Files changed (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -65
  3. package/adapters/README.md +30 -15
  4. package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
  5. package/adapters/claude-code/README.md +110 -8
  6. package/adapters/claude-code/commands/onboarding.md +21 -0
  7. package/adapters/claude-code/commands/ork.md +99 -27
  8. package/adapters/claude-code/hooks/hooks.json +6 -1
  9. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  10. package/adapters/codex/skills/ork/SKILL.md +57 -0
  11. package/adapters/hermes/README.md +163 -0
  12. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  13. package/adapters/hermes/bin/ork-brain.sh +4 -0
  14. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  15. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  16. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  17. package/adapters/hermes/bin/ork-objective-message.sh +17 -0
  18. package/adapters/hermes/bin/ork-objective-status.sh +12 -0
  19. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  20. package/adapters/hermes/hermes.plugin.json +26 -4
  21. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  22. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  24. package/adapters/openclaw/README.md +135 -26
  25. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  26. package/adapters/openclaw/construir.sh +19 -0
  27. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  28. package/adapters/openclaw/dist/index.js +437 -0
  29. package/adapters/openclaw/openclaw.plugin.json +39 -134
  30. package/adapters/openclaw/package.json +26 -0
  31. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  32. package/adapters/openclaw/src/index.ts +458 -0
  33. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  34. package/adapters/openclaw/tsconfig.json +15 -0
  35. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  36. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  37. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  38. package/assets/docs/produto/README.md +14 -0
  39. package/assets/docs/produto/_modelo-feature.md +59 -0
  40. package/assets/docs/roadmap/README.md +14 -0
  41. package/assets/docs/roadmap/_modelo-item.md +73 -0
  42. package/assets/orkmind-native-schema.json +32 -0
  43. package/assets/orkmind_bridge.py +357 -0
  44. package/assets/orkmind_fixture.py +67 -0
  45. package/assets/orkmind_prospective.py +138 -0
  46. package/assets/reference-tariffs-i07.json +23 -0
  47. package/dist/adapters/claude-bg.js +488 -39
  48. package/dist/adapters/codex-controller-sensor.js +449 -0
  49. package/dist/adapters/codex-controller-worker.js +361 -0
  50. package/dist/adapters/codex-controller.js +252 -0
  51. package/dist/adapters/codex-events.js +205 -0
  52. package/dist/adapters/codex-question.js +26 -0
  53. package/dist/adapters/codex-runner.js +133 -0
  54. package/dist/adapters/codex.js +394 -0
  55. package/dist/agents-md.js +84 -0
  56. package/dist/auditoria.js +11 -8
  57. package/dist/auditrun.js +5 -4
  58. package/dist/board.js +173 -22
  59. package/dist/branch-de-estado.js +136 -0
  60. package/dist/canarios-hitl.js +177 -0
  61. package/dist/canarios-i43.js +543 -0
  62. package/dist/canarios-pulse.js +147 -0
  63. package/dist/canarios-sensores.js +129 -0
  64. package/dist/canarios.js +111 -2
  65. package/dist/catalogo.js +9 -0
  66. package/dist/ci.js +216 -0
  67. package/dist/ciclos.js +2 -1
  68. package/dist/claim-lint.js +64 -0
  69. package/dist/claims.js +60 -0
  70. package/dist/company-brain-capture.js +195 -0
  71. package/dist/company-brain-cli.js +123 -0
  72. package/dist/company-brain-client.js +61 -0
  73. package/dist/company-brain-contract.js +166 -0
  74. package/dist/company-brain-journal.js +192 -0
  75. package/dist/company-brain-mcp.js +33 -0
  76. package/dist/company-brain-migration.js +60 -0
  77. package/dist/company-brain-source.js +177 -0
  78. package/dist/company-brain-worker.js +18 -0
  79. package/dist/conducao-texto.js +61 -0
  80. package/dist/conducao.js +876 -0
  81. package/dist/contrato-publico.js +39 -0
  82. package/dist/creation-operation-store.js +251 -0
  83. package/dist/creation-operation.js +148 -0
  84. package/dist/decisao-autonoma.js +183 -0
  85. package/dist/delegation.js +79 -0
  86. package/dist/demo.js +120 -0
  87. package/dist/docs.js +828 -0
  88. package/dist/doctor.js +183 -24
  89. package/dist/entrega-pr.js +117 -0
  90. package/dist/escopo-escrita.js +71 -0
  91. package/dist/estado-thread.js +222 -0
  92. package/dist/evalrunner.js +12 -0
  93. package/dist/fabrica-estado.js +329 -0
  94. package/dist/fabrica-publicar.js +76 -0
  95. package/dist/fix.js +21 -0
  96. package/dist/gates.js +55 -9
  97. package/dist/handoff.js +39 -23
  98. package/dist/hitl-canais.js +333 -0
  99. package/dist/hitl-classificacao.js +131 -0
  100. package/dist/hitl-contract.js +465 -0
  101. package/dist/hitl-estado.js +136 -0
  102. package/dist/hitl-gates.js +554 -0
  103. package/dist/hitl-ingress-receipt.js +382 -0
  104. package/dist/hitl-local-atestado.js +57 -0
  105. package/dist/hitl-local-receipt.js +328 -0
  106. package/dist/hitl-local.js +143 -0
  107. package/dist/hitl-lock.js +139 -0
  108. package/dist/hitl-lote.js +223 -0
  109. package/dist/hitl-native-offer.js +97 -0
  110. package/dist/hitl-native.js +65 -0
  111. package/dist/hitl-presentation.js +209 -0
  112. package/dist/hitl-public-receipt.js +176 -0
  113. package/dist/hitl-resumo.js +209 -0
  114. package/dist/hitl-sessions.js +475 -0
  115. package/dist/hitl.js +711 -0
  116. package/dist/horario.js +269 -0
  117. package/dist/hosts.js +198 -22
  118. package/dist/index.js +1736 -93
  119. package/dist/indice.js +99 -0
  120. package/dist/init.js +28 -3
  121. package/dist/integracoes-locais.js +17 -0
  122. package/dist/leases.js +65 -21
  123. package/dist/ledger-stats.js +272 -0
  124. package/dist/ledger.js +107 -2
  125. package/dist/licoes.js +215 -0
  126. package/dist/liveness.js +218 -0
  127. package/dist/maestro-actions.js +51 -0
  128. package/dist/maestro-authority.js +152 -0
  129. package/dist/maestro-cli.js +97 -0
  130. package/dist/maestro-contract.js +55 -0
  131. package/dist/maestro-discovery.js +132 -0
  132. package/dist/maestro-runtime.js +268 -0
  133. package/dist/maestro-snapshot.js +86 -0
  134. package/dist/maestro-sources.js +224 -0
  135. package/dist/manifest.js +165 -6
  136. package/dist/maquina.js +102 -0
  137. package/dist/master-audit.js +101 -0
  138. package/dist/master-batch.js +43 -0
  139. package/dist/master-digest.js +149 -0
  140. package/dist/master-migracao.js +155 -0
  141. package/dist/master.js +361 -77
  142. package/dist/mcp-artifacts.js +234 -0
  143. package/dist/mcp-git.js +433 -0
  144. package/dist/mcp-install.js +310 -0
  145. package/dist/mcp-maestro.js +25 -0
  146. package/dist/mcp-server.js +469 -0
  147. package/dist/mcp-ship.js +436 -0
  148. package/dist/mcp-verify.js +173 -0
  149. package/dist/memoria-humana.js +207 -0
  150. package/dist/memoria.js +314 -78
  151. package/dist/memory-migration.js +279 -0
  152. package/dist/memory-prospective.js +148 -0
  153. package/dist/modos-migracao.js +191 -0
  154. package/dist/modos.js +151 -15
  155. package/dist/monitor-lock.js +110 -0
  156. package/dist/objective.js +449 -0
  157. package/dist/ocupacao.js +267 -0
  158. package/dist/onboarding.js +303 -0
  159. package/dist/orkmind.js +389 -90
  160. package/dist/orquestracao.js +96 -69
  161. package/dist/phase.js +596 -150
  162. package/dist/playbook-capabilities.js +150 -0
  163. package/dist/playbook-contracts.js +188 -0
  164. package/dist/portfolio-context.js +61 -0
  165. package/dist/portfolio.js +180 -0
  166. package/dist/preflight.js +207 -0
  167. package/dist/process-audit.js +112 -0
  168. package/dist/project-state.js +93 -0
  169. package/dist/prompts.js +25 -5
  170. package/dist/prova-minima.js +112 -0
  171. package/dist/pulse-cadencia.js +163 -0
  172. package/dist/pulse-consentimento.js +260 -0
  173. package/dist/pulse-delivery.js +323 -0
  174. package/dist/pulse-resposta.js +542 -0
  175. package/dist/pulse.js +262 -0
  176. package/dist/ratelimit.js +35 -2
  177. package/dist/recall.js +80 -3
  178. package/dist/redacao-saida.js +45 -0
  179. package/dist/redacao-url.js +66 -0
  180. package/dist/retry.js +637 -115
  181. package/dist/roadmap-reservas.js +243 -0
  182. package/dist/runtime-ambiente.js +38 -0
  183. package/dist/runtime-context.js +215 -0
  184. package/dist/runtime-profiles.js +869 -0
  185. package/dist/runtimes.js +103 -0
  186. package/dist/sandbox.js +9 -0
  187. package/dist/session-events.js +203 -0
  188. package/dist/session-watcher-claude.js +637 -0
  189. package/dist/session-watcher.js +683 -0
  190. package/dist/sessoes-adopt.js +154 -0
  191. package/dist/sessoes-inventario.js +159 -0
  192. package/dist/sessoes.js +16 -35
  193. package/dist/setup.js +691 -0
  194. package/dist/ship.js +150 -15
  195. package/dist/slug.js +1 -1
  196. package/dist/thread-close.js +115 -0
  197. package/dist/thread.js +142 -28
  198. package/dist/tokens.js +1 -1
  199. package/dist/util.js +4 -2
  200. package/dist/verify-sandbox.js +265 -0
  201. package/dist/verify.js +270 -25
  202. package/dist/versao.js +60 -0
  203. package/dist/worktree.js +31 -2
  204. package/dist/write-activation.js +291 -0
  205. package/dist/yaml.js +2 -0
  206. package/eval/casos/onboarding.json +102 -0
  207. package/eval/casos/orkastery-bootstrap.json +35 -1
  208. package/eval/casos/scope-check-capability-map.json +45 -7
  209. package/eval/casos/ship-release.json +2 -2
  210. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  211. package/eval/fixtures/b2-master-log/caso.json +1 -1
  212. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  213. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  214. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  215. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  216. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  217. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  218. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  219. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  220. package/eval/fixtures/fx-happy/caso.json +6 -4
  221. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  222. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  223. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  224. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  225. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  226. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  227. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  228. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  229. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  230. package/monitor/company-brain.cjs +17 -0
  231. package/monitor/pulse-scope.cjs +45 -0
  232. package/monitor/pulse.cron +18 -0
  233. package/monitor/varredura-pulse.sh +27 -0
  234. package/package.json +25 -4
  235. package/schemas/claims.schema.json +25 -0
  236. package/schemas/company-brain.schema.json +1158 -0
  237. package/schemas/creation-operation.schema.json +365 -0
  238. package/schemas/maestro-snapshot.schema.json +2716 -0
  239. package/skills/core/onboarding/SKILL.md +50 -0
  240. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  241. package/skills/core/thread-state/SKILL.md +3 -1
  242. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
package/dist/indice.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * O indice automatico de qualidade da entrega (I-43, D3).
4
+ *
5
+ * Ele existe para substituir a fila de ratificacao de score, e o problema que a fila
6
+ * tinha nao era falta de lembrete: eram 13 entradas esperando nota humana justamente
7
+ * porque lembrete nao converte em nota. A saida e ACEITACAO POR DEFAULT com registro:
8
+ * entregue e aceito, a menos que o dono diga o contrario, e a nota humana passa a
9
+ * existir so quando ele reclamar, e ai vale.
10
+ *
11
+ * DUAS REGRAS DURAS, e elas explicam quase todo o resto deste arquivo.
12
+ *
13
+ * 1. So entra fato que tem ESCRITOR no nucleo e entrada no catalogo `TIPOS_DE_EVENTO`.
14
+ * A medida que motivou isso: os 137 ledgers do projeto trazem mais de 200 valores
15
+ * distintos de `tipo`, muitos com uma ocorrencia so, com variantes do mesmo fato
16
+ * (`lease_acquire` x `lease_acquired`, `claim_novo` x `claim_added`) e ate lixo
17
+ * literal (`B`, `path`, `commit`). Um indice que case string livre no ledger e um
18
+ * indice que mente: `verify_result` tem 122 ocorrencias e ZERO escritor no nucleo.
19
+ *
20
+ * 2. O indice e DERIVADO, nunca digitado. Nao existe caminho que aceite um numero de
21
+ * fora. E a diferenca entre este campo e o `score`, que hoje mistura tres coisas:
22
+ * das 19 notas em disco, 9 nao sao juizo humano de qualidade (3 sao carimbo cujo
23
+ * proprio texto diz "ratificacao pendente", 3 comecam com "Proponho" e sao proposta
24
+ * de agente, e 3 sao `valor: 0` de encerramento administrativo).
25
+ *
26
+ * RESERVA HONESTA, que precisa estar escrita aqui e nao so no PLAN: o indice NAO
27
+ * classifica o passado. Com os insumos que hoje tem escritor, 23 das 29 threads
28
+ * entregues ficariam coladas no teto, porque `rollback_done` tinha ZERO ocorrencias.
29
+ * Ele comeca a discriminar a partir do momento em que o escritor de reversao existir
30
+ * (T7, em `ship.ts`). Cobrar do indice um veredito sobre o historico seria cobrar um
31
+ * numero que ele nao tem como produzir.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.INSUMOS_DO_INDICE = exports.INDICE_MINIMO = exports.INDICE_BASE = void 0;
35
+ exports.calcularIndice = calcularIndice;
36
+ exports.textoDoIndice = textoDoIndice;
37
+ const ledger_1 = require("./ledger");
38
+ /** Nota maxima, e o ponto de partida: entregue e aceito ate prova em contrario. */
39
+ exports.INDICE_BASE = 5;
40
+ exports.INDICE_MINIMO = 0;
41
+ /**
42
+ * Os pesos de D3. Reversao pesa sozinha mais que tudo junto, e e de proposito:
43
+ * ela e o unico fato que diz, sem interpretacao, que a entrega nao servia.
44
+ */
45
+ exports.INSUMOS_DO_INDICE = [
46
+ { evento: ledger_1.TIPOS_DE_EVENTO.rollbackConcluido, peso: -3, piso: -3,
47
+ sobre: 'a entrega foi revertida depois do ship' },
48
+ { evento: ledger_1.TIPOS_DE_EVENTO.fixAberto, peso: -0.5, piso: -1.5,
49
+ sobre: 'rodadas de GO-FIX: o CHECK reprovou e a entrega precisou de correcao' },
50
+ { evento: ledger_1.TIPOS_DE_EVENTO.reverifyConcluido, peso: -0.5, piso: -1,
51
+ sobre: 'CHECK refeito: a verificacao precisou de uma segunda rodada' },
52
+ { evento: ledger_1.TIPOS_DE_EVENTO.shipBloqueado, peso: -0.25, piso: -0.5,
53
+ sobre: 'ship barrado antes de entregar' },
54
+ ];
55
+ /**
56
+ * Calcula o indice de uma thread a partir do ledger dela.
57
+ *
58
+ * PURA: le eventos, devolve numero, nao escreve nada e nao toca em git. Quem detecta
59
+ * reversao e `ship.ts`, que e quem sabe de merge e de base; aqui so se conta o evento
60
+ * que ele gravou.
61
+ */
62
+ function calcularIndice(eventos) {
63
+ const parcelas = [];
64
+ let valor = exports.INDICE_BASE;
65
+ for (const insumo of exports.INSUMOS_DO_INDICE) {
66
+ const ocorrencias = eventos.filter((e) => e.tipo === insumo.evento).length;
67
+ if (ocorrencias === 0)
68
+ continue;
69
+ const bruto = insumo.peso * ocorrencias;
70
+ const desconto = Math.max(bruto, insumo.piso);
71
+ parcelas.push({
72
+ evento: insumo.evento,
73
+ sobre: insumo.sobre,
74
+ ocorrencias,
75
+ desconto,
76
+ noPiso: bruto < insumo.piso,
77
+ });
78
+ valor += desconto;
79
+ }
80
+ return {
81
+ valor: Math.max(exports.INDICE_MINIMO, Math.min(exports.INDICE_BASE, Number(valor.toFixed(2)))),
82
+ base: exports.INDICE_BASE,
83
+ parcelas,
84
+ derivado: true,
85
+ };
86
+ }
87
+ /** Texto de uma linha por parcela, para `ork master` mostrar de onde o numero veio. */
88
+ function textoDoIndice(indice) {
89
+ const linhas = [`indice ${indice.valor} de ${indice.base} (derivado do ledger, nunca digitado)`];
90
+ if (indice.parcelas.length === 0) {
91
+ linhas.push(' nenhum insumo: a entrega nao registrou reversao, GO-FIX, CHECK refeito nem ship barrado');
92
+ return linhas.join('\n');
93
+ }
94
+ for (const p of indice.parcelas) {
95
+ linhas.push(` ${p.desconto.toFixed(2).padStart(6)} ${p.evento} x${p.ocorrencias}${p.noPiso ? ' (no piso)' : ''}`);
96
+ linhas.push(` ${p.sobre}`);
97
+ }
98
+ return linhas.join('\n');
99
+ }
package/dist/init.js CHANGED
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.PROXIMO_PASSO_INIT = void 0;
42
43
  exports.raizDoRepo = raizDoRepo;
43
44
  exports.nomeDetectado = nomeDetectado;
44
45
  exports.baseBranchDetectada = baseBranchDetectada;
@@ -51,6 +52,9 @@ const path = __importStar(require("node:path"));
51
52
  const manifest_1 = require("./manifest");
52
53
  const slug_1 = require("./slug");
53
54
  const util_1 = require("./util");
55
+ const horario_1 = require("./horario");
56
+ const modos_1 = require("./modos");
57
+ exports.PROXIMO_PASSO_INIT = 'Proximo passo: ork doctor; depois ork onboarding para conduzir a entrevista do projeto.';
54
58
  /** Raiz do repositorio git, resolvendo tambem o caso de worktree. */
55
59
  function raizDoRepo(dirInicial) {
56
60
  const r = (0, util_1.exec)('git', ['rev-parse', '--show-toplevel'], dirInicial);
@@ -148,22 +152,34 @@ project:
148
152
  abbrev: "${d.abbrev}"
149
153
  stage: nascente
150
154
 
155
+ owner:
156
+ # fuso do dono (nome IANA): todo horario mostrado a pessoas usa este fuso.
157
+ # Ausente: fuso do sistema. Exemplo:
158
+ # timezone: "${horario_1.FUSO_DE_BRASILIA}"
159
+
151
160
  board:
152
161
  adapter: hermes-kanban
153
162
  default: default
154
163
 
155
164
  runtime:
156
- # claude-bg despacha \`claude --bg\` (sessao visivel no app e em \`claude agents\`)
165
+ # Runtimes homologados:
166
+ # claude-bg (padrao, mais maduro): despacha \`claude --bg\`, sessao visivel no app
167
+ # e em \`claude agents --json\`
168
+ # codex: despacha \`codex exec\` headless na sessao autenticada do Codex CLI,
169
+ # verificado no rollout de $CODEX_HOME/sessions (requer \`codex login\`)
157
170
  adapter: claude-bg
158
171
  model: opus
159
172
  effort: high
160
- # subscription-only: proibido despachar por provider pago (licao dos US$ 16)
173
+ # subscription-only: proibido despachar por provider pago (cobra por token, fora da assinatura)
161
174
  provider_policy: subscription-only
175
+ # sandbox do runtime codex: read-only | workspace-write | danger-full-access
176
+ # (danger-full-access SO onde o bubblewrap nao roda; \`ork doctor\` tem a sonda)
177
+ sandbox: workspace-write
162
178
 
163
179
  conduction:
164
180
  # modo usado quando o pedido do builder nao traz #TAG
165
181
  default_mode: classic
166
- allowed_modes: [look, ork, classic, maestro, auto]
182
+ allowed_modes: [${modos_1.ORDEM_DOS_MODOS.join(', ')}]
167
183
 
168
184
  worktree:
169
185
  base_branch: "${d.baseBranch}"
@@ -175,8 +191,17 @@ ${linhaVerify('build', d.verify.build)}
175
191
  ${linhaVerify('test', d.verify.test)}
176
192
  ${linhaVerify('typecheck', d.verify.typecheck)}
177
193
 
194
+ ci:
195
+ # Ative depois de publicar o workflow e tornar ork-verify um check obrigatório.
196
+ required_for_ship: false
197
+ context: ork-verify
198
+ # command: "npm test" # perfil hermético opcional do runner; sem ele, usa verify acima
199
+
178
200
  concurrency:
179
201
  max_parallel_threads: 3
202
+ # thread despachada sem atividade (ledger parado, sessao nao-working) por mais que
203
+ # isto NAO ocupa vaga do escalonador: a vaga volta para quem esta pronto para rodar
204
+ stale_after_min: 240
180
205
 
181
206
  handoff:
182
207
  # gate de tokens (bloco B1): rotaciona a sessao acima destas ocupacoes de janela
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TESTES_DE_INTEGRACAO_LOCAL = void 0;
4
+ /**
5
+ * I-53 (RM-037, D17): a lista unica dos testes que exigem integracao local.
6
+ *
7
+ * Sao os arquivos que dependem de recurso deliberadamente ausente no runner hospedado do GitHub
8
+ * (Docker, OrkMind instalado, sessao de runtime). Antes a lista vivia duplicada em `ci.ts` e em
9
+ * `scripts/test-ci.js`, e as duas copias podiam divergir sem ninguem ver. Agora `ci.ts` importa
10
+ * daqui, e o script le o mesmo modulo ja compilado em `dist/` (o `test:ci` compila antes).
11
+ */
12
+ exports.TESTES_DE_INTEGRACAO_LOCAL = new Set([
13
+ 'codex-controller-sensor.test.js', 'consulta-restrita-nativa.test.js', 'decision-identity.test.js',
14
+ 'mcp-git.test.js', 'mcp-server.test.js', 'mcp-ship.test.js', 'mcp-verify.test.js',
15
+ 'memory-native-preparation.test.js', 'memory-native-schema.test.js', 'memory-prospective.test.js',
16
+ 'memory-publication-profile.test.js', 'native-fixture.test.js', 'orkmind-transport.test.js', 'verify-sandbox.test.js',
17
+ ]);
package/dist/leases.js CHANGED
@@ -48,12 +48,14 @@ var __importStar = (this && this.__importStar) || (function () {
48
48
  };
49
49
  })();
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.NOME_ARQUIVO_FILA = exports.FAMILIAS_DE_LEASE = exports.TTL_PADRAO_MS = exports.LEASE_MAIN_TREE = void 0;
51
+ exports.NOME_ARQUIVO_FILA = exports.PREFIXO_EXEC = exports.FAMILIAS_DE_LEASE = exports.TTL_PADRAO_MS = exports.LEASE_MAIN_TREE = void 0;
52
52
  exports.dirLeases = dirLeases;
53
+ exports.dirLeasesDeExecucao = dirLeasesDeExecucao;
53
54
  exports.nomeDeLease = nomeDeLease;
54
55
  exports.tipoDoLease = tipoDoLease;
55
56
  exports.alvoDoLease = alvoDoLease;
56
57
  exports.caminhoLease = caminhoLease;
58
+ exports.regravarLease = regravarLease;
57
59
  exports.lerLease = lerLease;
58
60
  exports.expirado = expirado;
59
61
  exports.globParaRegex = globParaRegex;
@@ -77,7 +79,10 @@ exports.tabelaDeLeases = tabelaDeLeases;
77
79
  const fs = __importStar(require("node:fs"));
78
80
  const path = __importStar(require("node:path"));
79
81
  const manifest_1 = require("./manifest");
82
+ const estado_thread_1 = require("./estado-thread");
80
83
  const util_1 = require("./util");
84
+ const horario_1 = require("./horario");
85
+ const conducao_texto_1 = require("./conducao-texto");
81
86
  /** Nome do lease que serializa o merge na base (visao, paridade 5). */
82
87
  exports.LEASE_MAIN_TREE = 'main-tree';
83
88
  /** TTL padrao de 30 minutos: um SHIP que passe disso deixou de existir. */
@@ -89,11 +94,22 @@ exports.FAMILIAS_DE_LEASE = {
89
94
  path: 'uma regiao de arquivos por glob (colisao entre threads paralelas)',
90
95
  board: 'um card do board, para duas threads nao pegarem o mesmo cartao',
91
96
  service: 'uma porta de servico local, para dois runs nao subirem no mesmo lugar',
97
+ exec: 'a EXECUCAO na worktree de uma thread (I-36): um condutor por vez, em qualquer canal',
92
98
  };
99
+ /** I-36 (D1): prefixo da familia de execucao, `exec:<thread>`. */
100
+ exports.PREFIXO_EXEC = 'exec';
93
101
  /** Diretorio dos leases do projeto. */
94
102
  function dirLeases(raiz) {
95
103
  return path.join((0, manifest_1.dirEstado)(raiz), 'leases');
96
104
  }
105
+ /**
106
+ * I-36: o lease de execucao mora no estado CANONICO do projeto (o mesmo das threads), nunca no
107
+ * checkout de quem chamou. Um canal que roda da raiz e outro que roda de uma worktree disputam o
108
+ * mesmo arquivo; com o diretorio do checkout, cada um teria o seu e os dois executariam juntos.
109
+ */
110
+ function dirLeasesDeExecucao(raiz) {
111
+ return path.join((0, manifest_1.dirEstado)((0, estado_thread_1.raizDoEstado)(raiz)), 'leases');
112
+ }
97
113
  /**
98
114
  * Nome canonico de um lease de familia tipada: `path:core/src/**`, `service:5173`.
99
115
  * `main-tree` nao tem alvo: e um so no projeto.
@@ -111,7 +127,8 @@ function tipoDoLease(nome) {
111
127
  if (nome === exports.LEASE_MAIN_TREE)
112
128
  return 'main-tree';
113
129
  const prefixo = nome.split(':')[0];
114
- if (prefixo === 'worktree-write' || prefixo === 'path' || prefixo === 'board' || prefixo === 'service') {
130
+ if (prefixo === 'worktree-write' || prefixo === 'path' || prefixo === 'board' || prefixo === 'service' ||
131
+ prefixo === exports.PREFIXO_EXEC) {
115
132
  return prefixo;
116
133
  }
117
134
  return 'path';
@@ -127,7 +144,19 @@ function alvoDoLease(nome) {
127
144
  * inalterado, o que preserva os leases gravados pelo bloco B1.
128
145
  */
129
146
  function caminhoLease(raiz, nome) {
130
- return path.join(dirLeases(raiz), `${encodeURIComponent(nome)}.json`);
147
+ const dir = tipoDoLease(nome) === 'exec' ? dirLeasesDeExecucao(raiz) : dirLeases(raiz);
148
+ return path.join(dir, `${encodeURIComponent(nome)}.json`);
149
+ }
150
+ /**
151
+ * I-36 (D3): regrava um lease que a propria operacao segura (renovacao do prazo e conversao do
152
+ * dono), por arquivo temporario e rename: quem le nunca ve o arquivo pela metade.
153
+ */
154
+ function regravarLease(raiz, lease) {
155
+ const caminho = caminhoLease(raiz, lease.nome);
156
+ fs.mkdirSync(path.dirname(caminho), { recursive: true });
157
+ const temporario = `${caminho}.${process.pid}.tmp`;
158
+ fs.writeFileSync(temporario, JSON.stringify(lease, null, 2) + '\n', { encoding: 'utf8', mode: 0o644 });
159
+ fs.renameSync(temporario, caminho);
131
160
  }
132
161
  /** Le o lease em disco. Arquivo corrompido conta como lease ausente. */
133
162
  function lerLease(raiz, nome) {
@@ -253,7 +282,7 @@ function adquirir(raiz, nome, opcoes) {
253
282
  if (e.code !== 'EEXIST')
254
283
  throw e;
255
284
  const atual = lerLease(raiz, nome);
256
- if (atual && !expirado(atual)) {
285
+ if (opcoes.retomarVencido === false || (atual && !expirado(atual))) {
257
286
  return { ok: false, lease: null, ocupadoPor: atual, tomadoDeVencido: false };
258
287
  }
259
288
  // Lease vencido ou corrompido: remove e tenta uma unica vez mais.
@@ -292,23 +321,32 @@ function liberar(raiz, nome, thread, forcar = false) {
292
321
  }
293
322
  /** Todos os leases existentes no projeto, pelo nome canonico gravado no arquivo. */
294
323
  function listarLeases(raiz) {
295
- const dir = dirLeases(raiz);
296
- if (!fs.existsSync(dir))
297
- return [];
298
324
  const leases = [];
299
- for (const arquivo of fs.readdirSync(dir)) {
300
- if (!arquivo.endsWith('.json') || arquivo === exports.NOME_ARQUIVO_FILA)
301
- continue;
302
- try {
303
- const bruto = fs.readFileSync(path.join(dir, arquivo), 'utf8');
304
- const lease = JSON.parse(bruto);
305
- if (lease && typeof lease.nome === 'string')
325
+ const ler = (dir, soExecucao) => {
326
+ if (!fs.existsSync(dir))
327
+ return;
328
+ for (const arquivo of fs.readdirSync(dir)) {
329
+ if (!arquivo.endsWith('.json') || arquivo === exports.NOME_ARQUIVO_FILA)
330
+ continue;
331
+ try {
332
+ const lease = JSON.parse(fs.readFileSync(path.join(dir, arquivo), 'utf8'));
333
+ if (!lease || typeof lease.nome !== 'string')
334
+ continue;
335
+ if (soExecucao && tipoDoLease(lease.nome) !== 'exec')
336
+ continue;
306
337
  leases.push(lease);
338
+ }
339
+ catch {
340
+ /* arquivo corrompido conta como lease ausente */
341
+ }
307
342
  }
308
- catch {
309
- /* arquivo corrompido conta como lease ausente */
310
- }
311
- }
343
+ };
344
+ // I-36: os do checkout, mais os de execucao do estado canonico quando ele e outro diretorio.
345
+ const local = dirLeases(raiz), canonico = dirLeasesDeExecucao(raiz);
346
+ const real = (dir) => (fs.existsSync(dir) ? fs.realpathSync(dir) : path.resolve(dir));
347
+ ler(local, false);
348
+ if (real(local) !== real(canonico))
349
+ ler(canonico, true);
312
350
  return leases.sort((a, b) => a.nome.localeCompare(b.nome));
313
351
  }
314
352
  /** Leases ativos (nao vencidos) que colidem com o nome pedido. */
@@ -488,8 +526,12 @@ function tabelaDeLeases(raiz) {
488
526
  for (const l of leases) {
489
527
  const situacao = expirado(l) ? 'VENCIDO (tomavel)' : 'ativo';
490
528
  linhas.push(` ${l.nome.padEnd(28)} [${tipoDoLease(l.nome)}] thread ${l.thread.padEnd(18)} ${situacao}`);
491
- linhas.push(` desde ${l.adquiridoEm} ate ${l.expiraEm} (pid ${l.pid})`);
529
+ linhas.push(` desde ${(0, horario_1.formatarDataHora)(l.adquiridoEm)} ate ${(0, horario_1.formatarDataHora)(l.expiraEm)} (pid ${l.pid})`);
492
530
  linhas.push(` motivo: ${l.motivo}`);
531
+ // I-36: o lease de execucao diz quem conduz, com a mesma linha das outras superficies.
532
+ const conducao = (0, conducao_texto_1.conducaoDoLease)(l);
533
+ if (conducao)
534
+ linhas.push(` ${(0, conducao_texto_1.linhaDeConducao)(conducao)}`);
493
535
  }
494
536
  }
495
537
  linhas.push('');
@@ -497,12 +539,14 @@ function tabelaDeLeases(raiz) {
497
539
  const regiao = fila.filter((p) => p.tipo !== 'main-tree');
498
540
  linhas.push(`Fila de merge (lease ${exports.LEASE_MAIN_TREE}): ${merge.length === 0 ? 'vazia' : `${merge.length} na espera`}`);
499
541
  merge.forEach((p, i) => {
500
- linhas.push(` ${i + 1}. thread ${p.thread} desde ${p.desdeEm} (bloqueada por ${p.bloqueadaPor})`);
542
+ linhas.push(` ${i + 1}. thread ${p.thread} desde ${(0, horario_1.formatarDesde)(p.desdeEm)} (bloqueada por ${p.bloqueadaPor})`);
501
543
  });
502
544
  linhas.push(`Fila por colisao de regiao: ${regiao.length === 0 ? 'vazia' : `${regiao.length} na espera`}`);
503
545
  regiao.forEach((p, i) => {
504
546
  linhas.push(` ${i + 1}. thread ${p.thread} quer ${p.nome}, colide com ${p.colidiuCom} ` +
505
- `(thread ${p.bloqueadaPor}) desde ${p.desdeEm}`);
547
+ `(thread ${p.bloqueadaPor}) desde ${(0, horario_1.formatarDesde)(p.desdeEm)}`);
506
548
  });
549
+ if (leases.length > 0 || fila.length > 0)
550
+ linhas.push((0, horario_1.legendaDoFuso)());
507
551
  return linhas.join('\n');
508
552
  }
@@ -0,0 +1,272 @@
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.parsePeriodo = parsePeriodo;
37
+ exports.coletarEstatisticas = coletarEstatisticas;
38
+ exports.registrarEstimativaPlano = registrarEstimativaPlano;
39
+ exports.textoDasEstatisticas = textoDasEstatisticas;
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const thread_1 = require("./thread");
43
+ const ledger_1 = require("./ledger");
44
+ const util_1 = require("./util");
45
+ const horario_1 = require("./horario");
46
+ const MOTIVOS_PREVENTIVOS = new Set([
47
+ 'policy.violation', 'cost.violation', 'verify.regression', 'claims.failed', 'claims.unverifiable',
48
+ ]);
49
+ function numero(valor) {
50
+ return typeof valor === 'number' && Number.isFinite(valor) && valor >= 0 ? valor : null;
51
+ }
52
+ function parsePeriodo(valor, agoraMs = Date.now()) {
53
+ if (typeof valor === 'number') {
54
+ if (!Number.isFinite(valor))
55
+ throw Error('ledger.period.invalid');
56
+ return valor;
57
+ }
58
+ const relativo = /^(\d+(?:\.\d+)?)(m|h|d)$/.exec(valor.trim());
59
+ if (relativo) {
60
+ const fator = relativo[2] === 'm' ? 60_000 : relativo[2] === 'h' ? 3_600_000 : 86_400_000;
61
+ return agoraMs - Number(relativo[1]) * fator;
62
+ }
63
+ const absoluto = Date.parse(valor);
64
+ if (!Number.isFinite(absoluto))
65
+ throw Error(`ledger.period.invalid: ${valor}`);
66
+ return absoluto;
67
+ }
68
+ function lerTarifas() {
69
+ const candidatos = [
70
+ path.resolve(__dirname, '..', 'assets', 'reference-tariffs-i07.json'),
71
+ path.resolve(__dirname, '..', '..', 'assets', 'reference-tariffs-i07.json'),
72
+ ];
73
+ const arquivo = candidatos.find(fs.existsSync);
74
+ if (!arquivo)
75
+ throw Error('ledger.tariffs.unavailable');
76
+ return JSON.parse(fs.readFileSync(arquivo, 'utf8'));
77
+ }
78
+ function instante(evento) {
79
+ const valor = Date.parse(evento.ts);
80
+ return Number.isFinite(valor) ? valor : null;
81
+ }
82
+ function chaveResultado(e, indice) {
83
+ const sensor = typeof e.sensorResultId === 'string' ? e.sensorResultId : null;
84
+ const sessao = typeof e.sessionId === 'string' ? e.sessionId : null;
85
+ return sensor ?? (sessao ? `${sessao}:${String(e.fase ?? '')}` : `linha:${indice}`);
86
+ }
87
+ function modeloDoEvento(e, despachos) {
88
+ if (typeof e.model === 'string')
89
+ return e.model;
90
+ const sessao = typeof e.sessionId === 'string' ? e.sessionId : '';
91
+ const d = despachos.get(sessao);
92
+ return typeof d?.model === 'string' ? d.model : null;
93
+ }
94
+ /** Agrega somente fatos observados no ledger; ausencia aparece como cobertura, nunca como zero inventado. */
95
+ function coletarEstatisticas(raiz, filtros) {
96
+ const agoraMs = filtros.agoraMs ?? Date.now();
97
+ const desdeMs = parsePeriodo(filtros.desde, agoraMs);
98
+ const ateMs = filtros.ate === undefined ? agoraMs : parsePeriodo(filtros.ate, agoraMs);
99
+ if (desdeMs >= ateMs)
100
+ throw Error('ledger.period.order');
101
+ if (filtros.thread && !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(filtros.thread))
102
+ throw Error('ledger.thread.invalid');
103
+ const tarifas = lerTarifas();
104
+ const candidatas = (0, thread_1.listarIds)(raiz).map(id => (0, thread_1.lerThread)(raiz, id)).filter(t => !filtros.thread || t.id === filtros.thread);
105
+ if (filtros.thread && candidatas.length === 0)
106
+ throw Error(`thread nao encontrada: ${filtros.thread}`);
107
+ const eventosPorThread = candidatas.map(t => ({ thread: t, eventos: (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id)) }));
108
+ const selecionadas = eventosPorThread.filter(({ thread, eventos }) => {
109
+ const criada = eventos.find(e => e.tipo === 'thread_created');
110
+ const modo = typeof criada?.modo === 'string' ? criada.modo : thread.modo;
111
+ return !filtros.modo || modo === filtros.modo;
112
+ });
113
+ let despachadas = 0, concluidas = 0, retries = 0, ships = 0, duracao = 0;
114
+ let duracaoMedida = 0, duracaoAusente = 0, tokenMedido = 0, tokenAusente = 0;
115
+ let input = 0, cachedInput = 0, output = 0, total = 0, custo = 0, precificadas = 0, semTarifa = 0;
116
+ let esperaMs = 0, esperasFechadas = 0, esperasAbertas = 0, corrompidos = 0;
117
+ let estimativas = 0, semIaHoras = 0, iaSemOrkHoras = 0;
118
+ const riscos = {};
119
+ const leads = [];
120
+ const fontes = new Set();
121
+ for (const { eventos } of selecionadas) {
122
+ corrompidos += eventos.filter(e => e.tipo === 'ledger_corrupted').length;
123
+ const despachos = new Map();
124
+ for (const e of eventos)
125
+ if (e.tipo === 'phase_dispatch' && typeof e.sessionId === 'string')
126
+ despachos.set(e.sessionId, e);
127
+ const dentro = (e) => { const t = instante(e); return t !== null && t >= desdeMs && t < ateMs; };
128
+ const runtimeOk = (e) => {
129
+ if (!filtros.runtime)
130
+ return true;
131
+ if (e.runtime === filtros.runtime)
132
+ return true;
133
+ const sessao = typeof e.sessionId === 'string' ? e.sessionId : '';
134
+ return despachos.get(sessao)?.runtime === filtros.runtime;
135
+ };
136
+ despachadas += eventos.filter(e => e.tipo === 'phase_dispatch' && dentro(e) && runtimeOk(e)).length;
137
+ retries += eventos.filter(e => e.tipo === 'retry_attempt' && dentro(e)).length;
138
+ ships += eventos.filter(e => e.tipo === 'ship_done' && dentro(e)).length;
139
+ const resultados = new Map();
140
+ eventos.forEach((e, i) => {
141
+ if (e.tipo === 'phase_result' && dentro(e) && runtimeOk(e))
142
+ resultados.set(chaveResultado(e, i), e);
143
+ });
144
+ for (const e of resultados.values()) {
145
+ concluidas++;
146
+ const ms = numero(e.duracaoMs);
147
+ if (ms === null)
148
+ duracaoAusente++;
149
+ else {
150
+ duracao += ms;
151
+ duracaoMedida++;
152
+ }
153
+ const t = e.tokens && typeof e.tokens === 'object' ? e.tokens : null;
154
+ const tin = numero(t?.input), tcached = numero(t?.cachedInput), tout = numero(t?.output), ttotal = numero(t?.total);
155
+ if (!t || t.disponivel === false || tin === null || tcached === null || tout === null) {
156
+ tokenAusente++;
157
+ continue;
158
+ }
159
+ tokenMedido++;
160
+ input += tin;
161
+ cachedInput += Math.min(tcached, tin);
162
+ output += tout;
163
+ total += ttotal ?? tin + tout;
164
+ const modelo = modeloDoEvento(e, despachos);
165
+ const tarifa = modelo ? tarifas.models[modelo] : undefined;
166
+ if (!tarifa) {
167
+ semTarifa++;
168
+ continue;
169
+ }
170
+ custo += ((tin - Math.min(tcached, tin)) * tarifa.input + Math.min(tcached, tin) * tarifa.cachedInput + tout * tarifa.output) / tarifas.unitTokens;
171
+ precificadas++;
172
+ fontes.add(tarifa.source);
173
+ }
174
+ const criada = eventos.find(e => e.tipo === 'thread_created');
175
+ const criadaMs = criada ? instante(criada) : null;
176
+ for (const e of eventos.filter(e => e.tipo === 'ship_done' && dentro(e))) {
177
+ const fim = instante(e);
178
+ if (criadaMs !== null && fim !== null && fim >= criadaMs)
179
+ leads.push(fim - criadaMs);
180
+ }
181
+ const pedidos = new Map();
182
+ for (const e of eventos) {
183
+ if (e.tipo === 'gate_blocked' && e.motivo === 'human.pending') {
184
+ const chave = typeof e.pedidoId === 'string' ? e.pedidoId : `${String(e.fase ?? '')}:legacy`;
185
+ const t = instante(e);
186
+ if (t !== null)
187
+ (pedidos.get(chave) ?? (pedidos.set(chave, []), pedidos.get(chave))).push(t);
188
+ }
189
+ if (e.tipo === 'human_gate') {
190
+ const chave = typeof e.pedidoId === 'string' ? e.pedidoId : `${String(e.fase ?? '')}:legacy`;
191
+ const inicio = pedidos.get(chave)?.shift();
192
+ const fim = instante(e);
193
+ if (inicio !== undefined && fim !== null && fim >= inicio) {
194
+ const a = Math.max(inicio, desdeMs), b = Math.min(fim, ateMs);
195
+ if (b > a)
196
+ esperaMs += b - a;
197
+ if (fim >= desdeMs && fim < ateMs)
198
+ esperasFechadas++;
199
+ }
200
+ }
201
+ }
202
+ for (const aberturas of pedidos.values())
203
+ for (const inicio of aberturas) {
204
+ const a = Math.max(inicio, desdeMs);
205
+ if (ateMs > a)
206
+ esperaMs += ateMs - a;
207
+ if (inicio < ateMs)
208
+ esperasAbertas++;
209
+ }
210
+ for (const e of eventos.filter(e => e.tipo === 'gate_blocked' && dentro(e))) {
211
+ const motivo = typeof e.motivo === 'string' ? e.motivo : '';
212
+ if (MOTIVOS_PREVENTIVOS.has(motivo))
213
+ riscos[motivo] = (riscos[motivo] ?? 0) + 1;
214
+ }
215
+ for (const e of eventos.filter(e => e.tipo === 'plan_estimate' && dentro(e))) {
216
+ const a = numero(e.semIaHoras), b = numero(e.iaSemOrkHoras);
217
+ if (a !== null && b !== null) {
218
+ estimativas++;
219
+ semIaHoras += a;
220
+ iaSemOrkHoras += b;
221
+ }
222
+ }
223
+ }
224
+ const observacoes = [];
225
+ if (tokenAusente)
226
+ observacoes.push(`${tokenAusente} sessao(oes) sem medicao de tokens`);
227
+ if (semTarifa)
228
+ observacoes.push(`${semTarifa} sessao(oes) com tokens, mas sem tarifa de referencia`);
229
+ if (corrompidos)
230
+ observacoes.push(`${corrompidos} linha(s) de ledger corrompida(s) ignorada(s)`);
231
+ return {
232
+ schema: 'ork.ledger-stats/v1', periodo: { desde: new Date(desdeMs).toISOString(), ate: new Date(ateMs).toISOString(), intervalo: '[desde,ate)' },
233
+ filtros: { thread: filtros.thread ?? null, modo: filtros.modo ?? null, runtime: filtros.runtime ?? null }, threads: selecionadas.length,
234
+ sessoes: { despachadas, concluidas, retries }, throughput: { ships },
235
+ execucao: { milissegundos: duracao, horas: duracao / 3_600_000, cobertura: { medidas: duracaoMedida, semMedida: duracaoAusente } },
236
+ tokens: { input, cachedInput, uncachedInput: input - cachedInput, output, total, cobertura: { medidas: tokenMedido, semMedida: tokenAusente } },
237
+ custoReferencia: { natureza: tarifas.nature, moeda: tarifas.currency, valor: precificadas ? Number(custo.toFixed(6)) : null, sessoesPrecificadas: precificadas, sessoesSemTarifa: semTarifa, tarifasVerificadasEm: tarifas.verifiedAt, fontes: [...fontes].sort() },
238
+ leadTime: { mediaMs: leads.length ? leads.reduce((a, b) => a + b, 0) / leads.length : null, amostras: leads.length },
239
+ esperaHumana: { milissegundos: esperaMs, fechadas: esperasFechadas, abertas: esperasAbertas },
240
+ riscosPreventivos: { total: Object.values(riscos).reduce((a, b) => a + b, 0), porMotivo: riscos },
241
+ estimativasPlano: { amostras: estimativas, semIaHoras, iaSemOrkHoras },
242
+ qualidade: { ledgersCorrompidos: corrompidos, observacoes },
243
+ };
244
+ }
245
+ function registrarEstimativaPlano(raiz, threadId, dados) {
246
+ if (![dados.semIaHoras, dados.iaSemOrkHoras].every(v => Number.isFinite(v) && v > 0))
247
+ throw Error('ledger.estimate.hours-positive');
248
+ if (![dados.por, dados.metodo, dados.premissas].every(v => v.trim() !== ''))
249
+ throw Error('ledger.estimate.provenance-required');
250
+ const thread = (0, thread_1.listarIds)(raiz).map(id => (0, thread_1.lerThread)(raiz, id)).find(t => t.id === threadId);
251
+ if (!thread)
252
+ throw Error(`thread nao encontrada: ${threadId}`);
253
+ const head = (0, util_1.exec)('git', ['rev-parse', 'HEAD'], raiz);
254
+ if (!head.ok)
255
+ throw Error('ledger.estimate.head-unavailable');
256
+ return (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, 'plan_estimate', { fase: 'PLAN', unidade: 'hours', semIaHoras: dados.semIaHoras, iaSemOrkHoras: dados.iaSemOrkHoras, por: dados.por, metodo: dados.metodo, premissas: dados.premissas, incerteza: dados.incerteza ?? 'nao informada', head: head.stdout.trim() });
257
+ }
258
+ function textoDasEstatisticas(r) {
259
+ const dinheiro = r.custoReferencia.valor === null ? 'sem cobertura' : `USD ${r.custoReferencia.valor.toFixed(6)}`;
260
+ return [
261
+ `Ledger stats ${(0, horario_1.formatarDataHora)(r.periodo.desde)} ate ${(0, horario_1.formatarDataHoraRotulada)(r.periodo.ate)} ${r.periodo.intervalo}`,
262
+ ` threads/sessoes ${r.threads} / ${r.sessoes.concluidas} concluidas (${r.sessoes.despachadas} despachos)`,
263
+ ` throughput ${r.throughput.ships} ship(s); ${r.sessoes.retries} retry(ies)`,
264
+ ` execucao ${r.execucao.horas.toFixed(2)} h (${r.execucao.cobertura.medidas} medidas; ${r.execucao.cobertura.semMedida} ausentes)`,
265
+ ` tokens ${r.tokens.total} total; input ${r.tokens.input} (cached ${r.tokens.cachedInput}); output ${r.tokens.output}`,
266
+ ` custo referencia ${dinheiro} — estimativa por tarifa publica, nao gasto/fatura`,
267
+ ` espera humana ${(r.esperaHumana.milissegundos / 3_600_000).toFixed(2)} h; ${r.esperaHumana.abertas} aberta(s)`,
268
+ ` riscos evitados ${r.riscosPreventivos.total} gate(s) preventivo(s)`,
269
+ ` estimativas PLAN ${r.estimativasPlano.amostras} amostra(s)`,
270
+ ...r.qualidade.observacoes.map(o => ` cobertura ${o}`),
271
+ ].join('\n');
272
+ }