@orkastery/cli 0.5.1 → 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.
Files changed (260) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/agents/ork-master.md +2 -0
  3. package/adapters/claude-code/commands/master.md +8 -0
  4. package/adapters/claude-code/commands/ork.md +9 -0
  5. package/adapters/claude-code/mods/autoridade.json +56 -0
  6. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  8. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  9. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  10. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  11. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  12. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  13. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  16. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  17. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  18. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  19. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  20. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  21. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  25. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  26. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  27. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  28. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  29. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  30. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  33. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  34. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  35. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  36. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  37. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  38. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  44. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  45. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  46. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  47. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  48. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  49. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  52. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  53. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  54. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  55. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  56. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  57. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  59. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  60. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  61. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  62. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  63. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  64. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  67. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  68. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  69. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  70. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  71. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  72. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  77. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  78. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  79. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  80. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  81. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  82. package/adapters/codex/skills/ork/SKILL.md +18 -3
  83. package/adapters/hermes/README.md +1 -0
  84. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  85. package/adapters/hermes/hermes.plugin.json +1 -0
  86. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  87. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +7 -6
  88. package/adapters/openclaw/README.md +26 -5
  89. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  90. package/adapters/openclaw/dist/index.js +46 -11
  91. package/adapters/openclaw/openclaw.plugin.json +5 -1
  92. package/adapters/openclaw/package.json +2 -2
  93. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  94. package/adapters/openclaw/src/index.ts +49 -11
  95. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  96. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  97. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  98. package/assets/docs-en/produto/README.md +14 -0
  99. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  100. package/assets/docs-en/roadmap/README.md +14 -0
  101. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  102. package/assets/orkmind_bridge.py +117 -9
  103. package/dist/adapters/claude-bg.js +116 -18
  104. package/dist/adapters/codex.js +14 -4
  105. package/dist/agents-md.js +24 -14
  106. package/dist/auditrun.js +22 -3
  107. package/dist/board.js +20 -17
  108. package/dist/branch-de-estado.js +88 -10
  109. package/dist/busca-semantica.js +52 -12
  110. package/dist/canarios-rm057.js +149 -0
  111. package/dist/canarios.js +2 -0
  112. package/dist/catalogo.js +33 -0
  113. package/dist/ci.js +21 -8
  114. package/dist/claims-conferir.js +342 -0
  115. package/dist/claims.js +41 -1
  116. package/dist/company-brain-client.js +36 -1
  117. package/dist/company-brain-context.js +158 -22
  118. package/dist/company-brain-journal.js +2 -1
  119. package/dist/company-brain-migration.js +36 -1
  120. package/dist/conducao-sentinela.js +388 -0
  121. package/dist/conducao-texto.js +12 -18
  122. package/dist/conducao.js +122 -30
  123. package/dist/conhecimento.js +490 -0
  124. package/dist/docs.js +70 -17
  125. package/dist/doctor-pulse-cron.js +349 -0
  126. package/dist/doctor-rede.js +150 -0
  127. package/dist/doctor.js +195 -111
  128. package/dist/entrega-pr.js +42 -29
  129. package/dist/escopo-escrita.js +10 -1
  130. package/dist/esgotamentos.js +181 -0
  131. package/dist/estado-thread.js +23 -1
  132. package/dist/experiencia-instalacao.js +45 -25
  133. package/dist/experiencia.js +12 -2
  134. package/dist/fabrica-batida.js +310 -0
  135. package/dist/fabrica-estado.js +52 -14
  136. package/dist/fabrica-publicar.js +9 -1
  137. package/dist/fechamento.js +20 -66
  138. package/dist/fila-pesada.js +444 -0
  139. package/dist/fix.js +3 -2
  140. package/dist/forja.js +31 -7
  141. package/dist/gates.js +3 -24
  142. package/dist/handoff.js +10 -6
  143. package/dist/hitl-contract.js +59 -4
  144. package/dist/hitl-curto.js +11 -6
  145. package/dist/hitl-gates.js +5 -0
  146. package/dist/hitl-local-receipt.js +2 -0
  147. package/dist/hitl-lote.js +5 -2
  148. package/dist/hitl-resumo.js +38 -3
  149. package/dist/hitl-tempo-parado.js +201 -0
  150. package/dist/hitl-texto-livre.js +5 -5
  151. package/dist/hitl.js +29 -33
  152. package/dist/horario.js +41 -7
  153. package/dist/hosts.js +104 -11
  154. package/dist/impedimento.js +299 -0
  155. package/dist/index.js +961 -542
  156. package/dist/indice-vetorial.js +87 -10
  157. package/dist/indice.js +9 -1
  158. package/dist/init.js +56 -43
  159. package/dist/instabilidade.js +144 -0
  160. package/dist/integracoes-locais.js +4 -0
  161. package/dist/intelligence-graph-cli.js +170 -16
  162. package/dist/intelligence-graph-contexto.js +254 -0
  163. package/dist/intelligence-graph-contract.js +2 -1
  164. package/dist/intelligence-graph-extract-md.js +56 -4
  165. package/dist/intelligence-graph-extract-ts.js +71 -3
  166. package/dist/intelligence-graph-parsers.js +6 -4
  167. package/dist/intelligence-graph-query.js +66 -19
  168. package/dist/leases.js +664 -87
  169. package/dist/ledger-stats.js +15 -1
  170. package/dist/ledger.js +18 -1
  171. package/dist/licoes.js +1 -0
  172. package/dist/liveness.js +6 -3
  173. package/dist/locale-ajuda.js +761 -0
  174. package/dist/locale-gerados.js +160 -0
  175. package/dist/locale-master.js +99 -0
  176. package/dist/locale-onboarding.js +92 -0
  177. package/dist/locale-phase.js +151 -0
  178. package/dist/locale-pulse.js +211 -0
  179. package/dist/locale-retry.js +385 -0
  180. package/dist/locale-roadmap.js +102 -0
  181. package/dist/locale-ship.js +494 -0
  182. package/dist/locale-thread.js +277 -0
  183. package/dist/locale-verify.js +125 -0
  184. package/dist/locale.js +714 -0
  185. package/dist/maestro-runtime.js +4 -1
  186. package/dist/manifest.js +149 -4
  187. package/dist/maquina.js +23 -4
  188. package/dist/master-digest.js +6 -1
  189. package/dist/master-nota.js +18 -11
  190. package/dist/master.js +145 -31
  191. package/dist/mcp-artifacts.js +43 -3
  192. package/dist/mcp-git.js +42 -8
  193. package/dist/mcp-grafo-worker.js +5 -3
  194. package/dist/mcp-grafo.js +55 -6
  195. package/dist/mcp-server.js +22 -3
  196. package/dist/mcp-ship.js +3 -1
  197. package/dist/mcp-verify.js +6 -1
  198. package/dist/memoria.js +48 -6
  199. package/dist/modos.js +3 -6
  200. package/dist/network-roadmap.js +196 -18
  201. package/dist/noctum-arco.js +911 -0
  202. package/dist/noctum-capacidade.js +141 -0
  203. package/dist/noctum-envelope.js +810 -0
  204. package/dist/noctum-execucao.js +304 -0
  205. package/dist/noctum-fila.js +280 -0
  206. package/dist/ocupacao.js +40 -5
  207. package/dist/onboarding.js +40 -21
  208. package/dist/orkmind.js +99 -7
  209. package/dist/orquestracao.js +24 -21
  210. package/dist/parado-no-condutor.js +219 -38
  211. package/dist/phase.js +160 -52
  212. package/dist/policies.js +56 -5
  213. package/dist/portfolio.js +8 -3
  214. package/dist/postura-local.js +234 -0
  215. package/dist/preflight.js +11 -10
  216. package/dist/procedencia.js +225 -0
  217. package/dist/projeto-alvo.js +132 -5
  218. package/dist/prompts.js +40 -1
  219. package/dist/prova-ativacao.js +75 -6
  220. package/dist/pulse-delivery.js +47 -4
  221. package/dist/pulse.js +98 -19
  222. package/dist/ratelimit.js +24 -6
  223. package/dist/recall.js +3 -1
  224. package/dist/redacao-saida.js +63 -0
  225. package/dist/rede-adesao.js +399 -0
  226. package/dist/rede-forja.js +289 -0
  227. package/dist/rede-projetos.js +239 -0
  228. package/dist/rede-status.js +337 -0
  229. package/dist/rede.js +980 -0
  230. package/dist/retry.js +291 -140
  231. package/dist/roadmap-reservas.js +20 -3
  232. package/dist/roadmap-status.js +150 -34
  233. package/dist/runtime-context.js +181 -3
  234. package/dist/runtime-profiles.js +22 -1
  235. package/dist/runtimes.js +2 -1
  236. package/dist/saida-segura.js +58 -0
  237. package/dist/session-events.js +7 -3
  238. package/dist/session-watcher-claude.js +120 -24
  239. package/dist/session-watcher.js +185 -33
  240. package/dist/sessoes-contas.js +187 -0
  241. package/dist/sessoes-inventario.js +6 -18
  242. package/dist/sessoes.js +83 -2
  243. package/dist/ship-publicar.js +800 -0
  244. package/dist/ship.js +157 -88
  245. package/dist/steal.js +77 -0
  246. package/dist/thread-close.js +3 -2
  247. package/dist/thread.js +299 -49
  248. package/dist/util.js +8 -1
  249. package/dist/verify.js +141 -52
  250. package/dist/vida-da-thread.js +254 -0
  251. package/dist/worktree-local.js +165 -0
  252. package/dist/worktree.js +25 -2
  253. package/dist/yaml.js +27 -8
  254. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  255. package/package.json +7 -3
  256. package/references/definition-of-done.md +1 -1
  257. package/schemas/code-artifact-graph.v1.schema.json +2 -1
  258. package/skills/README.md +1 -1
  259. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  260. package/skills/phases/master-metrics/SKILL.md +8 -3
@@ -16,12 +16,15 @@ exports.registrarEntregasPorPr = registrarEntregasPorPr;
16
16
  * remota (ancestralidade conferida contra a ponta que o `ls-remote` devolve) e o check do CI no
17
17
  * head do PR. Sem CI verde, nao registra: merge sem prova independente nao e entrega.
18
18
  */
19
+ const branch_de_estado_1 = require("./branch-de-estado");
19
20
  const ci_1 = require("./ci");
21
+ const docs_1 = require("./docs");
20
22
  const fabrica_publicar_1 = require("./fabrica-publicar");
21
23
  const ledger_1 = require("./ledger");
22
24
  const modos_1 = require("./modos");
23
25
  const thread_1 = require("./thread");
24
26
  const util_1 = require("./util");
27
+ const locale_1 = require("./locale");
25
28
  /** O merge `ship(<thread>)` mais recente na base remota, com o head do PR (segundo pai). */
26
29
  function mergeDaEntrega(raiz, thread, remoto, base) {
27
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);
@@ -49,7 +52,7 @@ function avancarParaShip(raiz, threadId) {
49
52
  (0, thread_1.gravarThread)(raiz, atualizada);
50
53
  }
51
54
  function resultado(thread, acao, motivo, extra = {}) {
52
- return { thread, acao, motivo, mergeSha: null, headSha: null, ci: null, ...extra };
55
+ return { thread, acao, motivo, mergeSha: null, headSha: null, ci: null, docsPendentes: [], ...extra };
53
56
  }
54
57
  /**
55
58
  * `ork ship registrar-pr <thread>`: grava o `ship_done` da entrega feita por PR. Idempotente:
@@ -57,39 +60,46 @@ function resultado(thread, acao, motivo, extra = {}) {
57
60
  */
58
61
  function registrarEntregaPorPr(carregado, threadId, opcoes = {}) {
59
62
  const raiz = carregado.raiz;
60
- const remoto = opcoes.remoto ?? 'origin';
63
+ // RM-047: o `--remoto` vem da linha de comando; so nome de remoto chega ao git, e depois do `--`.
64
+ const remoto = (0, branch_de_estado_1.exigirRemoto)(opcoes.remoto ?? 'origin', 'ship');
61
65
  const base = carregado.manifesto.worktree.base_branch;
62
66
  const thread = (0, thread_1.lerThread)(raiz, threadId);
63
67
  if (thread.status === 'fechada')
64
- return resultado(threadId, 'ja-registrada', 'thread ja fechada');
68
+ return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prThreadFechada));
65
69
  if (opcoes.buscar !== false)
66
- (0, util_1.exec)('git', ['fetch', '-q', remoto, `+refs/heads/${base}:refs/remotes/${remoto}/${base}`], raiz);
67
- const merge = mergeDaEntrega(raiz, threadId, remoto, base);
68
- if (!merge)
69
- return resultado(threadId, 'sem-merge', `nenhum merge ship(${threadId}) em ${remoto}/${base}`);
70
+ (0, util_1.exec)('git', ['fetch', '-q', '--', remoto, `+refs/heads/${base}:refs/remotes/${remoto}/${base}`], raiz);
71
+ const encontrado = mergeDaEntrega(raiz, threadId, remoto, base);
72
+ if (!encontrado)
73
+ return resultado(threadId, 'sem-merge', (0, locale_1.textoDoCatalogo)((m) => m.ship.prSemMerge(threadId, remoto, base)));
74
+ // RM-044 (A3): o condutor roda o registrar-pr logo depois do merge; e aqui que ele fica sabendo da pagina que
75
+ // vai deixar o push da base vermelho, lida da base remota que o fetch acabou de trazer.
76
+ const merge = { ...encontrado, docsPendentes: (0, docs_1.docsPendentesDoMerge)(raiz, threadId, `refs/remotes/${remoto}/${base}`) };
70
77
  const dir = (0, thread_1.dirThread)(raiz, threadId);
71
78
  const jaTem = (0, ledger_1.lerLedger)(dir).some((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.shipConcluido && e.mergeSha === merge.mergeSha);
72
79
  if (jaTem)
73
- return resultado(threadId, 'ja-registrada', 'esta entrega ja tem ship_done', merge);
80
+ return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prJaTemShipDone), merge);
74
81
  // A ponta do remoto AGORA, e o merge dentro dela: e o que torna o push provado, nao presumido.
75
- const remotoAgora = (0, util_1.exec)('git', ['ls-remote', remoto, `refs/heads/${base}`], raiz);
82
+ const remotoAgora = (0, util_1.exec)('git', ['ls-remote', '--', remoto, `refs/heads/${base}`], raiz);
76
83
  const ponta = remotoAgora.ok ? remotoAgora.stdout.split('\t')[0].trim() : '';
77
84
  if (!ponta || !(0, util_1.exec)('git', ['merge-base', '--is-ancestor', merge.mergeSha, ponta], raiz).ok) {
78
- return resultado(threadId, 'recusada', `o merge ${merge.mergeSha.slice(0, 7)} nao esta na ponta de ${remoto}/${base}`, merge);
85
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prForaDaPonta(merge.mergeSha.slice(0, 7), remoto, base)), merge);
79
86
  }
80
87
  const ci = (0, ci_1.consultarCi)(carregado, merge.headSha ?? merge.mergeSha, remoto, opcoes.executorCi);
81
88
  if (ci.required && !ci.ok) {
82
- return resultado(threadId, 'recusada', `sem CI verde no head do PR: ${ci.detail}`, { ...merge, ci });
89
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prSemCiVerde((0, locale_1.remontar)(ci.detail))), { ...merge, ci });
83
90
  }
84
91
  // RM-037 (fatia 3, defeito 6): o ensaio confere tudo e para aqui, sem ledger, fase nem fabrica.
85
92
  if (opcoes.dryRun) {
86
- return resultado(threadId, 'registraria', `ensaio: registraria ship_done pelo merge ${merge.mergeSha.slice(0, 7)} do PR (nada gravado)`, { ...merge, ci });
93
+ return resultado(threadId, 'registraria', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRegistraria(merge.mergeSha.slice(0, 7))), { ...merge, ci });
87
94
  }
88
95
  (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipConcluido, {
89
96
  de: thread.base?.branch ?? null,
90
97
  para: base,
91
98
  shaDe: merge.headSha,
92
99
  mergeSha: merge.mergeSha,
100
+ // RM-037 (fatia 5, A1): a ponta que o ls-remote provou vai em campo proprio; o recibo do Maestro confere o push
101
+ // contra ela quando a base andou depois do merge.
102
+ pontaDaBase: ponta,
93
103
  jaIncorporado: true,
94
104
  remoto,
95
105
  shaRemoto: ponta,
@@ -105,7 +115,7 @@ function registrarEntregaPorPr(carregado, threadId, opcoes = {}) {
105
115
  avancarParaShip(raiz, threadId);
106
116
  if (opcoes.publicar !== false)
107
117
  (0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
108
- return resultado(threadId, 'registrou', `ship_done pelo merge ${merge.mergeSha.slice(0, 7)} do PR`, { ...merge, ci });
118
+ return resultado(threadId, 'registrou', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRegistrou(merge.mergeSha.slice(0, 7))), { ...merge, ci });
109
119
  }
110
120
  const ghApi = (caminho) => (0, util_1.exec)('gh', ['api', caminho, '-H', 'Accept: application/vnd.github+json'], process.cwd(), 120000);
111
121
  function lerJson(r) {
@@ -130,25 +140,25 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
130
140
  const gh = opcoes.executorGitHub ?? ghApi;
131
141
  const declarados = carregado.manifesto.ci.external_repositories;
132
142
  if (!Object.hasOwn(declarados, repo)) {
133
- return resultado(threadId, 'recusada', `o repositorio ${repo} nao esta declarado em ci.external_repositories do orkastery.yaml`);
143
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRepoNaoDeclarado(repo)));
134
144
  }
135
145
  if (!Number.isSafeInteger(n) || n < 1)
136
- return resultado(threadId, 'recusada', `--pr precisa ser o numero do PR, recebido ${String(n)}`);
146
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prNumeroInvalido(String(n))));
137
147
  const thread = (0, thread_1.lerThread)(raiz, threadId);
138
148
  if (thread.status === 'fechada')
139
- return resultado(threadId, 'ja-registrada', 'thread ja fechada');
149
+ return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prThreadFechada));
140
150
  const dir = (0, thread_1.dirThread)(raiz, threadId);
141
151
  if ((0, ledger_1.lerLedger)(dir).some((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.shipConcluido && e.repositorio === repo && e.pr === n)) {
142
- return resultado(threadId, 'ja-registrada', `o PR #${n} de ${repo} ja tem ship_done`);
152
+ return resultado(threadId, 'ja-registrada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoJaTem(n, repo)));
143
153
  }
144
154
  const pr = lerJson(gh(`repos/${repo}/pulls/${n}`));
145
155
  if (!pr)
146
- return resultado(threadId, 'recusada', `a consulta do PR #${n} de ${repo} ao GitHub falhou`);
156
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prConsultaFalhou(n, repo)));
147
157
  const mergeSha = typeof pr.merge_commit_sha === 'string' && /^[0-9a-f]{40}$/.test(pr.merge_commit_sha) ? pr.merge_commit_sha : null;
148
158
  const headSha = typeof pr.head?.sha === 'string' && /^[0-9a-f]{40}$/.test(pr.head.sha) ? pr.head.sha : null;
149
159
  const base = typeof pr.base?.ref === 'string' && /^[A-Za-z0-9._/-]{1,200}$/.test(pr.base.ref) ? pr.base.ref : null;
150
160
  if (pr.merged !== true || !mergeSha || !headSha || !base) {
151
- return resultado(threadId, 'sem-merge', `o PR #${n} de ${repo} nao esta mesclado`);
161
+ return resultado(threadId, 'sem-merge', (0, locale_1.textoDoCatalogo)((m) => m.ship.prNaoMesclado(n, repo)));
152
162
  }
153
163
  // Achado A4 do CHECK: o PR precisa ser DESTA thread e ter entrado na branch padrao do repositorio. Sem o
154
164
  // vinculo, qualquer PR mesclado do repositorio viraria entrega de qualquer thread; sem a base, um PR
@@ -158,28 +168,28 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
158
168
  // letra ou digito colado nao (outro id). Sem diferenciar maiusculas.
159
169
  .some((x) => new RegExp(`(^|[^a-z0-9-])${threadId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![a-z0-9])`, 'i').test(x));
160
170
  if (!vinculo) {
161
- return resultado(threadId, 'recusada', `o PR #${n} de ${repo} nao cita a thread ${threadId} no titulo, no corpo nem na branch`, { mergeSha, headSha });
171
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prNaoCitaThread(n, repo, threadId)), { mergeSha, headSha });
162
172
  }
163
173
  const repositorio = lerJson(gh(`repos/${repo}`));
164
174
  if (!repositorio)
165
- return resultado(threadId, 'recusada', `a consulta de ${repo} ao GitHub falhou`, { mergeSha, headSha });
175
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prRepoFalhou(repo)), { mergeSha, headSha });
166
176
  if (repositorio.default_branch !== base) {
167
- return resultado(threadId, 'recusada', `o PR #${n} entrou em ${base}, e a branch padrao de ${repo} e ${repositorio.default_branch ?? 'desconhecida'}`, { mergeSha, headSha });
177
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prOutraBase(n, base, repo, repositorio.default_branch)), { mergeSha, headSha });
168
178
  }
169
179
  // A ponta da base AGORA, e o merge dentro dela: o push provado, pela mesma pergunta do ls-remote.
170
180
  const ramo = lerJson(gh(`repos/${repo}/branches/${encodeURIComponent(base)}`));
171
181
  const ponta = typeof ramo?.commit?.sha === 'string' && /^[0-9a-f]{40}$/.test(ramo.commit.sha) ? ramo.commit.sha : '';
172
182
  const comparacao = ponta ? lerJson(gh(`repos/${repo}/compare/${mergeSha}...${ponta}`)) : null;
173
183
  if (!ponta || !comparacao || !['ahead', 'identical'].includes(String(comparacao.status))) {
174
- return resultado(threadId, 'recusada', `o merge ${mergeSha.slice(0, 7)} nao esta na ponta de ${repo}:${base}`, { mergeSha, headSha });
184
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoForaDaPonta(mergeSha.slice(0, 7), repo, base)), { mergeSha, headSha });
175
185
  }
176
186
  const check = declarados[repo];
177
187
  const ci = check ? (0, ci_1.consultarCiDoRepositorio)(repo, headSha, check, opcoes.executorCi) : null;
178
188
  if (ci && !ci.ok) {
179
- return resultado(threadId, 'recusada', `sem CI verde no head do PR: ${ci.detail}`, { mergeSha, headSha, ci });
189
+ return resultado(threadId, 'recusada', (0, locale_1.textoDoCatalogo)((m) => m.ship.prSemCiVerde((0, locale_1.remontar)(ci.detail))), { mergeSha, headSha, ci });
180
190
  }
181
191
  if (opcoes.dryRun) {
182
- return resultado(threadId, 'registraria', `ensaio: registraria ship_done pelo merge ${mergeSha.slice(0, 7)} do PR #${n} de ${repo} (nada gravado)`, { mergeSha, headSha, ci });
192
+ return resultado(threadId, 'registraria', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoRegistraria(mergeSha.slice(0, 7), n, repo)), { mergeSha, headSha, ci });
183
193
  }
184
194
  (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipConcluido, {
185
195
  de: thread.base?.branch ?? null,
@@ -189,6 +199,9 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
189
199
  url: pr.html_url ?? `https://github.com/${repo}/pull/${n}`,
190
200
  shaDe: headSha,
191
201
  mergeSha,
202
+ // RM-037 (fatia 5, A1; aviso da rodada 1 do CHECK): a ponta conferida vai em campo proprio, como no PR local; a
203
+ // comparacao aceita `ahead`, e o recibo do Maestro confere o push contra ela.
204
+ pontaDaBase: ponta,
192
205
  jaIncorporado: true,
193
206
  remoto: `github:${repo}`,
194
207
  shaRemoto: ponta,
@@ -203,13 +216,13 @@ function registrarEntregaExternaPorPr(carregado, threadId, opcoes) {
203
216
  avancarParaShip(raiz, threadId);
204
217
  if (opcoes.publicar !== false)
205
218
  (0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
206
- return resultado(threadId, 'registrou', `ship_done pelo merge ${mergeSha.slice(0, 7)} do PR #${n} de ${repo}`, { mergeSha, headSha, ci });
219
+ return resultado(threadId, 'registrou', (0, locale_1.textoDoCatalogo)((m) => m.ship.prExternoRegistrou(mergeSha.slice(0, 7), n, repo)), { mergeSha, headSha, ci });
207
220
  }
208
221
  /** `ork ship registrar-pr --todas`: toda thread aberta que ja tem merge `ship(<thread>)` na base. */
209
222
  function registrarEntregasPorPr(carregado, opcoes = {}) {
210
- const remoto = opcoes.remoto ?? 'origin';
223
+ const remoto = (0, branch_de_estado_1.exigirRemoto)(opcoes.remoto ?? 'origin', 'ship');
211
224
  const base = carregado.manifesto.worktree.base_branch;
212
- (0, util_1.exec)('git', ['fetch', '-q', remoto, `+refs/heads/${base}:refs/remotes/${remoto}/${base}`], carregado.raiz);
225
+ (0, util_1.exec)('git', ['fetch', '-q', '--', remoto, `+refs/heads/${base}:refs/remotes/${remoto}/${base}`], carregado.raiz);
213
226
  const r = (0, thread_1.listarIds)(carregado.raiz)
214
227
  .filter((id) => (0, thread_1.lerThread)(carregado.raiz, id).status !== 'fechada')
215
228
  .filter((id) => mergeDaEntrega(carregado.raiz, id, remoto, base) !== null)
@@ -218,7 +231,7 @@ function registrarEntregasPorPr(carregado, opcoes = {}) {
218
231
  return registrarEntregaPorPr(carregado, id, { ...opcoes, buscar: false, publicar: false });
219
232
  }
220
233
  catch (e) {
221
- return resultado(id, 'recusada', e.message);
234
+ return resultado(id, 'recusada', e instanceof locale_1.ErroDoCatalogo ? (0, locale_1.textoDoCatalogo)(e.montar) : e.message);
222
235
  }
223
236
  });
224
237
  // Uma publicacao so para o lote inteiro, nao uma por thread.
@@ -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
- const dir = (0, estado_thread_1.estadoCanonico)(raiz, id);
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)
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ /**
3
+ * RM-040 (B7): `ork accounts esgotamentos`, a medida da metrica do item.
4
+ *
5
+ * A metrica da RM-040 e "zero despacho numa conta que outro projeto do mesmo usuario ja viu
6
+ * esgotada, dentro do prazo dela". Este relatorio so le: as marcas do registro compartilhado
7
+ * (`~/.orkastery/private/contas.json`) e os `phase_dispatch` com perfil dos ledgers de cada projeto
8
+ * registrado nesta maquina (`~/.orkastery/projetos.json`). Conta o despacho que caiu na conta de
9
+ * uma marca, dentro da janela dela, vindo de projeto diferente do que marcou.
10
+ *
11
+ * A saida nunca leva diretorio de conta: a conta vira um id opaco (sha256 da chave, 12 hex), o
12
+ * projeto sai pelo nome registrado e o perfil, pelo id.
13
+ *
14
+ * Limite declarado: o registro so guarda as marcas vivas. Uso bem sucedido, login reconferido e
15
+ * prazo vencido apagam a marca na gravacao seguinte; por isso o numero e um piso, e a leitura vale
16
+ * enquanto a marca existe.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.NAO_REGISTRADO = exports.LIMITE_DO_RELATORIO = exports.CONTRATO_ESGOTAMENTOS = void 0;
53
+ exports.contaOpaca = contaOpaca;
54
+ exports.fimDaJanela = fimDaJanela;
55
+ exports.relatorioDeEsgotamentos = relatorioDeEsgotamentos;
56
+ exports.textoDosEsgotamentos = textoDosEsgotamentos;
57
+ const node_crypto_1 = require("node:crypto");
58
+ const fs = __importStar(require("node:fs"));
59
+ const horario_1 = require("./horario");
60
+ const ledger_1 = require("./ledger");
61
+ const ledger_stats_1 = require("./ledger-stats");
62
+ const projeto_alvo_1 = require("./projeto-alvo");
63
+ const runtime_profiles_1 = require("./runtime-profiles");
64
+ const thread_1 = require("./thread");
65
+ const util_1 = require("./util");
66
+ exports.CONTRATO_ESGOTAMENTOS = 'ork.esgotamentos/v1';
67
+ exports.LIMITE_DO_RELATORIO = 'o registro guarda so as marcas vivas (uso bem sucedido, login reconferido e prazo vencido ' +
68
+ 'apagam a marca): o total e um piso, valido enquanto a marca existe';
69
+ exports.NAO_REGISTRADO = 'nao registrado';
70
+ function contaOpaca(chave) {
71
+ return (0, node_crypto_1.createHash)('sha256').update(chave).digest('hex').slice(0, 12);
72
+ }
73
+ function caminhoReal(p) {
74
+ try {
75
+ return fs.realpathSync(p);
76
+ }
77
+ catch {
78
+ return p;
79
+ }
80
+ }
81
+ /** Fim da janela da marca: o prazo para cota esgotada; sem prazo (login, credencial paga), aberta. */
82
+ function fimDaJanela(m) {
83
+ if (m.estado !== 'esgotado')
84
+ return Number.POSITIVE_INFINITY;
85
+ const ate = m.esgotadoAte === null ? NaN : Date.parse(m.esgotadoAte);
86
+ return Number.isFinite(ate) ? ate : Number.NEGATIVE_INFINITY;
87
+ }
88
+ function relatorioDeEsgotamentos(opcoes = {}) {
89
+ const agoraMs = opcoes.agoraMs ?? Date.now();
90
+ const desdeMs = (0, ledger_stats_1.parsePeriodo)(opcoes.desde ?? '7d', agoraMs);
91
+ if (desdeMs >= agoraMs)
92
+ throw Error('ledger.period.order');
93
+ // Um projeto por raiz real: o registro pode trazer a mesma copia por dois caminhos.
94
+ const vistos = new Set();
95
+ const projetos = (0, projeto_alvo_1.listarProjetos)().filter((p) => {
96
+ const real = caminhoReal(p.raiz);
97
+ if (vistos.has(real))
98
+ return false;
99
+ vistos.add(real);
100
+ return true;
101
+ }).map((p) => ({ ...p, real: caminhoReal(p.raiz) }));
102
+ const nomeDaRaiz = (raiz) => projetos.find((p) => p.real === caminhoReal(raiz))?.nome ?? exports.NAO_REGISTRADO;
103
+ const contas = (0, runtime_profiles_1.lerContasCompartilhadas)();
104
+ const marcas = contas.map((c) => ({ c, real: caminhoReal(c.projeto), inicio: Date.parse(c.em), fim: fimDaJanela(c) }));
105
+ const despachos = [];
106
+ const lidos = [];
107
+ for (const p of projetos) {
108
+ let comPerfil = 0;
109
+ let ids = [];
110
+ try {
111
+ ids = p.presente ? (0, thread_1.listarIds)(p.raiz) : [];
112
+ }
113
+ catch {
114
+ ids = [];
115
+ }
116
+ for (const id of ids) {
117
+ let eventos = [];
118
+ try {
119
+ eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(p.raiz, id));
120
+ }
121
+ catch {
122
+ continue;
123
+ }
124
+ for (const e of eventos) {
125
+ if (e.tipo !== 'phase_dispatch' || e.perfil === undefined || e.perfil === null)
126
+ continue;
127
+ const t = Date.parse(String(e.ts));
128
+ if (!Number.isFinite(t) || t < desdeMs || t >= agoraMs)
129
+ continue;
130
+ let perfil, chave;
131
+ try {
132
+ perfil = (0, runtime_profiles_1.validarPerfilDeDespacho)(e.perfil);
133
+ chave = (0, runtime_profiles_1.chaveDaConta)(perfil);
134
+ }
135
+ catch {
136
+ continue;
137
+ }
138
+ comPerfil++;
139
+ const marca = marcas.find((m) => m.c.chave === chave && m.real !== p.real && t >= m.inicio && t < m.fim);
140
+ if (!marca)
141
+ continue;
142
+ despachos.push({ em: new Date(t).toISOString(), projeto: p.nome, thread: String(e.thread ?? id),
143
+ fase: typeof e.fase === 'string' ? e.fase : null, runtime: perfil.runtime, perfil: perfil.id,
144
+ conta: contaOpaca(chave), marcadaPor: nomeDaRaiz(marca.c.projeto) });
145
+ }
146
+ }
147
+ lidos.push({ nome: p.nome, presente: p.presente, despachosComPerfil: comPerfil });
148
+ }
149
+ despachos.sort((a, b) => a.em.localeCompare(b.em));
150
+ return {
151
+ contrato: exports.CONTRATO_ESGOTAMENTOS,
152
+ periodo: { desde: new Date(desdeMs).toISOString(), ate: new Date(agoraMs).toISOString(), intervalo: '[desde,ate)' },
153
+ marcas: contas.map((c) => ({ conta: contaOpaca(c.chave), runtime: c.chave.slice(0, c.chave.indexOf(':')), estado: c.estado,
154
+ motivo: c.motivo, em: c.em, esgotadoAte: c.esgotadoAte, projeto: nomeDaRaiz(c.projeto) })),
155
+ projetos: lidos,
156
+ despachos,
157
+ total: despachos.length,
158
+ limite: exports.LIMITE_DO_RELATORIO,
159
+ };
160
+ }
161
+ /** Texto para o terminal, com o horario do dono e o fuso uma vez no fim. */
162
+ function textoDosEsgotamentos(r) {
163
+ const linhas = [
164
+ `Esgotamentos de conta ${(0, horario_1.formatarDataHora)(r.periodo.desde)} ate ${(0, horario_1.formatarDataHora)(r.periodo.ate)} ${r.periodo.intervalo}`,
165
+ ` marcas vivas no registro: ${r.marcas.length}`,
166
+ ];
167
+ for (const m of r.marcas) {
168
+ const ate = m.estado === 'esgotado' ? `ate ${(0, horario_1.formatarDataHora)(m.esgotadoAte)}` : 'sem prazo';
169
+ linhas.push(` conta ${m.conta} ${m.runtime} ${m.estado} desde ${(0, horario_1.formatarDataHora)(m.em)}, ${ate} visto em ${m.projeto} (${m.motivo})`);
170
+ }
171
+ linhas.push(` projetos lidos: ${r.projetos.length === 0 ? 'nenhum registrado'
172
+ : r.projetos.map((p) => `${p.nome} (${p.presente ? `${p.despachosComPerfil} despacho(s) com perfil` : 'fora do disco'})`).join(', ')}`);
173
+ linhas.push(` despachos em conta marcada por outro projeto: ${r.total}`);
174
+ if (r.despachos.length > 0) {
175
+ linhas.push((0, util_1.tabela)(['QUANDO', 'PROJETO', 'THREAD', 'FASE', 'PERFIL', 'CONTA', 'MARCADA POR'], r.despachos.map((d) => [(0, horario_1.formatarDataHora)(d.em), d.projeto, d.thread, d.fase ?? '-', d.perfil, d.conta, d.marcadaPor]))
176
+ .split('\n').map((l) => ` ${l}`).join('\n'));
177
+ }
178
+ linhas.push(` limite: ${r.limite}`);
179
+ linhas.push((0, horario_1.legendaDoFuso)());
180
+ return linhas.join('\n');
181
+ }
@@ -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
- return path.join(raizDoEstado(raiz), '.orkastery', 'threads', id);
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
- const LINHA_DO_DIRETORIO = /^Se ativo, leia a skill selecionada pelo campo skill no diretório (".*")\.$/;
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
- /** O caminho vai relativo ao projeto: o arquivo de instruções costuma ser versionado. */
87
- function nucleoDoBloco(skill, eol) {
88
- return Buffer.from([exports.INICIO_EXPERIENCIA,
89
- '## Experiência de orquestração',
90
- 'Consulte `ork experiencia show --json` neste projeto. Se experience for false, não ative o pacote.',
91
- `Se ativo, leia a skill selecionada pelo campo skill no diretório ${JSON.stringify(skill)}.`,
92
- 'As preferências são dados do núcleo; preserve gates, proveniência humana e permissões do host.',
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; qualquer outra edição é conflito.
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 m = LINHA_DO_DIRETORIO.exec(linha);
112
- let skill;
113
- try {
114
- skill = m ? JSON.parse(m[1]) : undefined;
115
- }
116
- catch {
117
- return null;
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
- /** Planejamento puro em relação ao disco: dry-run e preflight usam o mesmo contrato. */
148
- function planejarExperiencia(projeto, host, skill) {
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;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IDIOMA_PADRAO = void 0;
3
4
  exports.normalizarIdioma = normalizarIdioma;
4
5
  exports.validarPreferencias = validarPreferencias;
5
6
  exports.idiomaDoSistema = idiomaDoSistema;
@@ -44,6 +45,11 @@ function validarPreferencias(valor) {
44
45
  }
45
46
  return resultado;
46
47
  }
48
+ /**
49
+ * Ensaio de 03/10 (R2): sem idioma escolhido, vale a língua da CLI. Antes era en-US, e uma máquina com
50
+ * LANG=C.UTF-8 (contêiner, CI, servidor) recebia a experiência em inglês de uma CLI que fala português.
51
+ */
52
+ exports.IDIOMA_PADRAO = 'pt-BR';
47
53
  /** C, POSIX e und (raiz do ICU) dizem que o sistema não escolheu idioma: vale o padrão. */
48
54
  const SEM_IDIOMA = new Set(['c', 'posix', 'und']);
49
55
  function idiomaEscolhido(valor) {
@@ -52,11 +58,15 @@ function idiomaEscolhido(valor) {
52
58
  }
53
59
  function idiomaDoSistema(env = process.env, doIntl = () => Intl.DateTimeFormat().resolvedOptions().locale) {
54
60
  const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG;
55
- return idiomaEscolhido(locale?.split(/[.@]/)[0].replace(/_/g, '-')) ?? idiomaEscolhido(doIntl()) ?? 'en-US';
61
+ if (locale) {
62
+ // Com C ou POSIX explícito, o Intl do Node cai no en-US do ICU: ele não diz a escolha de ninguém.
63
+ return idiomaEscolhido(locale.split(/[.@]/)[0].replace(/_/g, '-')) ?? exports.IDIOMA_PADRAO;
64
+ }
65
+ return idiomaEscolhido(doIntl()) ?? exports.IDIOMA_PADRAO;
56
66
  }
57
67
  function resolverExperiencia(owner = {}, sistema = { language: idiomaDoSistema(), timezone: (0, horario_1.fusoDoSistema)() }) {
58
68
  const p = validarPreferencias(owner);
59
- const language = p.language ?? normalizarIdioma(sistema.language) ?? 'en-US';
69
+ const language = p.language ?? normalizarIdioma(sistema.language) ?? exports.IDIOMA_PADRAO;
60
70
  return {
61
71
  language, timezone: p.timezone ?? (0, horario_1.normalizarFuso)(sistema.timezone) ?? 'UTC',
62
72
  depth: p.depth ?? 'curta', experience: p.experience ?? true,