@orkastery/cli 0.5.3 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/mods/autoridade.json +56 -0
  3. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  4. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  5. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  6. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  8. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  9. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  10. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  11. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  12. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  13. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  16. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  17. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  18. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  19. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  20. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  21. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  25. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  26. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  27. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  28. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  29. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  30. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  33. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  34. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  35. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  36. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  37. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  38. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  44. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  45. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  46. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  47. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  48. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  49. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  52. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  53. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  54. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  55. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  56. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  57. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  59. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  60. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  61. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  62. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  63. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  64. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  67. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  68. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  69. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  70. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  71. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  72. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  77. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  78. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  79. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
  80. package/adapters/openclaw/README.md +2 -1
  81. package/adapters/openclaw/openclaw.plugin.json +1 -1
  82. package/adapters/openclaw/package.json +1 -1
  83. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  84. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  85. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  86. package/assets/docs-en/produto/README.md +14 -0
  87. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  88. package/assets/docs-en/roadmap/README.md +14 -0
  89. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  90. package/dist/adapters/claude-bg.js +77 -13
  91. package/dist/adapters/codex.js +10 -1
  92. package/dist/agents-md.js +24 -15
  93. package/dist/auditrun.js +17 -2
  94. package/dist/board.js +4 -3
  95. package/dist/branch-de-estado.js +23 -1
  96. package/dist/catalogo.js +33 -0
  97. package/dist/ci.js +13 -7
  98. package/dist/claims-conferir.js +342 -0
  99. package/dist/claims.js +3 -1
  100. package/dist/company-brain-client.js +36 -1
  101. package/dist/company-brain-journal.js +2 -1
  102. package/dist/company-brain-migration.js +36 -1
  103. package/dist/conducao-sentinela.js +388 -0
  104. package/dist/conducao-texto.js +12 -18
  105. package/dist/conducao.js +122 -30
  106. package/dist/conhecimento.js +490 -0
  107. package/dist/docs.js +22 -8
  108. package/dist/doctor-pulse-cron.js +182 -27
  109. package/dist/doctor-rede.js +18 -17
  110. package/dist/doctor.js +115 -111
  111. package/dist/entrega-pr.js +22 -21
  112. package/dist/escopo-escrita.js +10 -1
  113. package/dist/estado-thread.js +23 -1
  114. package/dist/experiencia-instalacao.js +45 -25
  115. package/dist/fabrica-batida.js +310 -0
  116. package/dist/fabrica-estado.js +38 -8
  117. package/dist/fabrica-publicar.js +3 -1
  118. package/dist/fila-pesada.js +444 -0
  119. package/dist/fix.js +3 -2
  120. package/dist/forja.js +1 -1
  121. package/dist/gates.js +3 -28
  122. package/dist/handoff.js +10 -6
  123. package/dist/hitl-local-receipt.js +2 -0
  124. package/dist/hitl-resumo.js +29 -3
  125. package/dist/hitl-tempo-parado.js +4 -4
  126. package/dist/hitl.js +29 -33
  127. package/dist/horario.js +14 -7
  128. package/dist/hosts.js +52 -1
  129. package/dist/impedimento.js +108 -14
  130. package/dist/index.js +623 -521
  131. package/dist/indice.js +7 -1
  132. package/dist/init.js +43 -47
  133. package/dist/instabilidade.js +144 -0
  134. package/dist/integracoes-locais.js +2 -0
  135. package/dist/intelligence-graph-cli.js +92 -21
  136. package/dist/intelligence-graph-contexto.js +56 -17
  137. package/dist/intelligence-graph-contract.js +2 -1
  138. package/dist/intelligence-graph-extract-md.js +56 -4
  139. package/dist/intelligence-graph-extract-ts.js +71 -3
  140. package/dist/intelligence-graph-query.js +66 -19
  141. package/dist/ledger.js +15 -1
  142. package/dist/liveness.js +6 -3
  143. package/dist/locale-ajuda.js +761 -0
  144. package/dist/locale-gerados.js +160 -0
  145. package/dist/locale-master.js +99 -0
  146. package/dist/locale-onboarding.js +92 -0
  147. package/dist/locale-phase.js +151 -0
  148. package/dist/locale-pulse.js +211 -0
  149. package/dist/locale-retry.js +385 -0
  150. package/dist/locale-roadmap.js +102 -0
  151. package/dist/locale-ship.js +494 -0
  152. package/dist/locale-thread.js +277 -0
  153. package/dist/locale-verify.js +125 -0
  154. package/dist/locale.js +714 -0
  155. package/dist/manifest.js +129 -3
  156. package/dist/maquina.js +23 -4
  157. package/dist/master-digest.js +6 -1
  158. package/dist/master-nota.js +4 -6
  159. package/dist/master.js +21 -24
  160. package/dist/mcp-artifacts.js +11 -2
  161. package/dist/mcp-git.js +23 -6
  162. package/dist/mcp-grafo.js +7 -4
  163. package/dist/mcp-server.js +8 -2
  164. package/dist/mcp-ship.js +3 -1
  165. package/dist/mcp-verify.js +6 -1
  166. package/dist/modos.js +3 -6
  167. package/dist/network-roadmap.js +5 -2
  168. package/dist/noctum-arco.js +911 -0
  169. package/dist/noctum-capacidade.js +141 -0
  170. package/dist/noctum-envelope.js +810 -0
  171. package/dist/noctum-execucao.js +304 -0
  172. package/dist/noctum-fila.js +280 -0
  173. package/dist/ocupacao.js +29 -2
  174. package/dist/onboarding.js +40 -21
  175. package/dist/orkmind.js +9 -2
  176. package/dist/orquestracao.js +13 -12
  177. package/dist/parado-no-condutor.js +34 -9
  178. package/dist/phase.js +94 -39
  179. package/dist/policies.js +4 -4
  180. package/dist/portfolio.js +4 -2
  181. package/dist/postura-local.js +234 -0
  182. package/dist/preflight.js +11 -10
  183. package/dist/procedencia.js +225 -0
  184. package/dist/projeto-alvo.js +127 -4
  185. package/dist/prompts.js +6 -2
  186. package/dist/pulse-delivery.js +13 -2
  187. package/dist/pulse.js +52 -19
  188. package/dist/ratelimit.js +4 -2
  189. package/dist/redacao-saida.js +63 -0
  190. package/dist/rede-status.js +3 -2
  191. package/dist/retry.js +232 -148
  192. package/dist/roadmap-reservas.js +20 -3
  193. package/dist/roadmap-status.js +129 -33
  194. package/dist/runtime-context.js +181 -3
  195. package/dist/runtimes.js +2 -1
  196. package/dist/session-events.js +7 -3
  197. package/dist/session-watcher-claude.js +7 -1
  198. package/dist/session-watcher.js +185 -33
  199. package/dist/sessoes-contas.js +1 -1
  200. package/dist/ship-publicar.js +800 -0
  201. package/dist/ship.js +84 -81
  202. package/dist/steal.js +77 -0
  203. package/dist/thread-close.js +3 -2
  204. package/dist/thread.js +153 -82
  205. package/dist/util.js +8 -1
  206. package/dist/verify.js +141 -52
  207. package/dist/vida-da-thread.js +254 -0
  208. package/dist/worktree-local.js +165 -0
  209. package/dist/worktree.js +20 -0
  210. package/dist/yaml.js +27 -8
  211. package/package.json +1 -1
  212. package/schemas/code-artifact-graph.v1.schema.json +2 -1
package/dist/handoff.js CHANGED
@@ -73,6 +73,7 @@ const phase_1 = require("./phase");
73
73
  const thread_1 = require("./thread");
74
74
  const memoria_1 = require("./memoria");
75
75
  const util_1 = require("./util");
76
+ const procedencia_1 = require("./procedencia");
76
77
  /** sha256 de um arquivo em disco, a prova de proveniencia de cada item. */
77
78
  function sha256DoArquivo(caminho) {
78
79
  if (!fs.existsSync(caminho))
@@ -98,7 +99,8 @@ function caminhoDoArtefato(raiz, thread, arquivo) {
98
99
  ? [path.resolve(thread.worktree, arquivo), path.resolve(raiz, arquivo)]
99
100
  : [path.resolve(raiz, arquivo)];
100
101
  for (const c of candidatos) {
101
- if (fs.existsSync(c) && fs.statSync(c).isFile())
102
+ // RM-047 (P2): o arquivo da claim vem do estado; fora da raiz e da worktree registrada, não vira ponteiro.
103
+ if (fs.existsSync(c) && fs.statSync(c).isFile() && (0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, c))
102
104
  return c;
103
105
  }
104
106
  return null;
@@ -222,7 +224,7 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
222
224
  if (s.origem === 'adocao')
223
225
  continue;
224
226
  const caminhoPrompt = path.resolve(raiz, s.promptPath);
225
- if (!fs.existsSync(caminhoPrompt))
227
+ if (!fs.existsSync(caminhoPrompt) || !(0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, caminhoPrompt))
226
228
  continue;
227
229
  addPonteiro(`Prompt da sessao ${s.slug} (fase ${s.fase})`, caminhoPrompt, 'pedido-do-builder', s.fase);
228
230
  }
@@ -261,7 +263,7 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
261
263
  for (const e of anteriores) {
262
264
  const arquivo = (0, memoria_1.arquivoDoHandoff)(e);
263
265
  const emDisco = arquivo ? path.resolve(raiz, arquivo) : '';
264
- if (!emDisco || !fs.existsSync(emDisco))
266
+ if (!emDisco || !fs.existsSync(emDisco) || !(0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, emDisco))
265
267
  continue;
266
268
  addPonteiro(`Handoff anterior desta thread (${arquivo})`, emDisco, 'tudo', proximaFase, `orkmind://handoff/${e.id}`);
267
269
  }
@@ -270,7 +272,7 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
270
272
  for (const e of (0, memoria_1.licoesDoProduto)(memoria, manifesto, threadId)) {
271
273
  const postmortem = (0, memoria_1.postmortemDaLicao)(e);
272
274
  const emDisco = postmortem ? path.resolve(raiz, postmortem) : '';
273
- if (!emDisco || !fs.existsSync(emDisco))
275
+ if (!emDisco || !fs.existsSync(emDisco) || !(0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, emDisco))
274
276
  continue;
275
277
  addPonteiro(`Licao da thread ${(0, memoria_1.threadDaEntrada)(e) ?? '(anterior)'}`, emDisco, 'json:resumo', 'MASTER', `orkmind://learning/${e.id}`);
276
278
  }
@@ -398,9 +400,11 @@ function recall(carregado, threadId, ponteiro) {
398
400
  if (!fs.existsSync(caminho) || !fs.statSync(caminho).isFile()) {
399
401
  throw new Error(`ponteiro nao resolve: arquivo "${bruto}" nao existe a partir de ${raiz}`);
400
402
  }
401
- const conteudoBruto = fs.readFileSync(caminho, 'utf8');
403
+ // RM-047 (P2): o `location` vem do handoff.json (estado, talvez versionado); só lê dentro do projeto.
404
+ const real = (0, procedencia_1.exigirCaminhoContido)(raiz, (0, thread_1.lerThread)(raiz, threadId), caminho, bruto);
405
+ const conteudoBruto = fs.readFileSync(real, 'utf8');
402
406
  const linhas = conteudoBruto.split('\n');
403
- const sha = sha256DoArquivo(caminho);
407
+ const sha = sha256DoArquivo(real);
404
408
  const base = { location: `${bruto}#${ancora}`, caminho, ancora, sha256: sha };
405
409
  const registrarRecall = (r) => {
406
410
  (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.handoffRecuperado, {
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.chaveLocal = chaveLocal;
36
37
  exports.gravarEvidenciaLocal = gravarEvidenciaLocal;
37
38
  exports.gravarEvidenciaLocalSessao = gravarEvidenciaLocalSessao;
38
39
  exports.validarEvidenciaLocalSessao = validarEvidenciaLocalSessao;
@@ -70,6 +71,7 @@ function diretorioPrivado(file) {
70
71
  }
71
72
  return validarDiretorio(file, 0o700);
72
73
  }
74
+ /** RM-058 (N1): exportada só para o arco #Noctum derivar a subchave dele (`noctum-arco.ts`). */
73
75
  function chaveLocal(raiz, criar) {
74
76
  const estado = path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery');
75
77
  const stEstado = fs.lstatSync(estado);
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SEM_PERGUNTA_NO_RESUMO = exports.TETO_DE_DECISOES_DETALHADAS = exports.TETO_DE_ESPERAS_POR_MAQUINA = exports.TETO_DE_PARADOS_NO_CONDUTOR = exports.TETO_DE_THREADS_NOMEADAS = exports.PERGUNTA_DO_RESUMO = exports.CONTRATO_RESUMO = void 0;
3
+ exports.SEM_PERGUNTA_COM_IMPEDIMENTO = exports.SEM_PERGUNTA_NO_RESUMO = exports.TETO_DE_DECISOES_DETALHADAS = exports.TETO_DE_ESPERAS_POR_MAQUINA = exports.TETO_DE_IMPEDIMENTOS = exports.TETO_DE_PARADOS_NO_CONDUTOR = exports.TETO_DE_THREADS_NOMEADAS = exports.PERGUNTA_DO_RESUMO = exports.CONTRATO_RESUMO = void 0;
4
4
  exports.resumirHitl = resumirHitl;
5
5
  exports.linhaDoParadoNoCondutor = linhaDoParadoNoCondutor;
6
6
  exports.linhasDoCondutor = linhasDoCondutor;
7
+ exports.linhasDosImpedimentos = linhasDosImpedimentos;
7
8
  exports.linhaDosTecnicos = linhaDosTecnicos;
8
9
  exports.linhasDasOutrasMaquinas = linhasDasOutrasMaquinas;
9
10
  exports.linhasDasDecisoes = linhasDasDecisoes;
@@ -29,6 +30,7 @@ exports.textoDoResumo = textoDoResumo;
29
30
  * mensagem do canal. Quem entrega chama a segunda.
30
31
  */
31
32
  const horario_1 = require("./horario");
33
+ const locale_1 = require("./locale");
32
34
  const hitl_tempo_parado_1 = require("./hitl-tempo-parado");
33
35
  const decisao_autonoma_1 = require("./decisao-autonoma");
34
36
  const hitl_classificacao_1 = require("./hitl-classificacao");
@@ -60,6 +62,8 @@ function resumirHitl(itens, opcoes) {
60
62
  threadsTecnicas: [...new Set(tecnicos.map(i => i.thread).filter((t) => !!t))].sort(),
61
63
  ...(opcoes.paradosNoCondutor?.length ? { paradosNoCondutor: opcoes.paradosNoCondutor.map(p => ({ thread: p.thread, caso: p.caso,
62
64
  desdeEm: p.desdeEm, proximoPasso: p.proximoPasso })) } : {}),
65
+ ...(opcoes.impedimentosDoDono?.length ? { impedimentosDoDono: opcoes.impedimentosDoDono.map(i => ({ thread: i.thread,
66
+ fase: i.fase, comando: i.comando, retry: i.retry })) } : {}),
63
67
  ...(opcoes.hitlDeConducao?.acimaDaMeta ? { hitlDeConducao: opcoes.hitlDeConducao } : {}),
64
68
  };
65
69
  }
@@ -71,7 +75,7 @@ exports.TETO_DE_PARADOS_NO_CONDUTOR = 5;
71
75
  */
72
76
  function linhaDoParadoNoCondutor(p, opcoes) {
73
77
  const { desdeEm: desde } = p;
74
- return `${p.thread} parado no condutor desde ${(0, horario_1.formatarHora)(desde, opcoes)}: ${p.proximoPasso}`;
78
+ return (0, locale_1.msg)().pulse.linhaDoCondutor(p.thread, (0, horario_1.formatarHora)(desde, opcoes), p.proximoPasso);
75
79
  }
76
80
  /** As linhas do condutor no resumo, uma por thread, com o resto contado. */
77
81
  function linhasDoCondutor(r, marcador, recuo) {
@@ -80,6 +84,24 @@ function linhasDoCondutor(r, marcador, recuo) {
80
84
  return [...mostradas.map(p => `${marcador}${linhaDoParadoNoCondutor(p, { agora: r.consultadoEm })}`),
81
85
  ...(sobra > 0 ? [`${recuo}e mais ${sobra}: ork pulse`] : [])];
82
86
  }
87
+ /** Quantos impedimentos do dono o resumo detalha antes de contar o resto. */
88
+ exports.TETO_DE_IMPEDIMENTOS = 5;
89
+ /** Uma linha so, sem caractere de controle e sem corte: o comando precisa chegar inteiro para ser copiado. */
90
+ const semQuebra = (texto) => String(texto).replace(/\p{Cc}/gu, ' ').replace(/\s+/g, ' ').trim();
91
+ /**
92
+ * RM-055 (continuacao, D3): o titulo e uma linha por impedimento do dono, iguais nos dois canais (so o
93
+ * marcador e o recuo mudam). Cada linha diz a thread, a fase, o comando exato e o retry que vem depois.
94
+ */
95
+ function linhasDosImpedimentos(r, marcador, recuo) {
96
+ const impedimentos = r.impedimentosDoDono ?? [];
97
+ if (!impedimentos.length)
98
+ return [];
99
+ const mostrados = impedimentos.slice(0, exports.TETO_DE_IMPEDIMENTOS), sobra = impedimentos.length - mostrados.length;
100
+ return [`${marcador}Só você destrava, no terminal: ${impedimentos.length}`,
101
+ ...mostrados.map(i => `${recuo}• ${semQuebra(i.thread)}${i.fase ? ` ${semQuebra(i.fase)}` : ''}: ` +
102
+ `rode \`${semQuebra(i.comando)}\`; depois \`${semQuebra(i.retry)}\``),
103
+ ...(sobra > 0 ? [`${recuo}e mais ${sobra}: ork pulse`] : [])];
104
+ }
83
105
  /** RM-057 (fatia 3): a linha do tempo parado por HITL de conducao, a mesma nos dois canais. */
84
106
  function linhaDoHitlParado(r) {
85
107
  const linha = (0, hitl_tempo_parado_1.linhaDoHitlAcimaDaMeta)(r.hitlDeConducao, { agora: r.consultadoEm });
@@ -143,6 +165,8 @@ function linhasDasDecisoes(r, marcador, recuo) {
143
165
  }
144
166
  /** Quando nao ha pergunta, o resumo diz isso em vez de pedir licenca para mandar nada. */
145
167
  exports.SEM_PERGUNTA_NO_RESUMO = 'Nada aqui pede resposta sua por este canal agora.';
168
+ /** RM-055 (continuacao, D3): sem pergunta, mas com impedimento do dono: o que fazer esta no bloco acima. */
169
+ exports.SEM_PERGUNTA_COM_IMPEDIMENTO = 'Nada aqui pede resposta sua por este canal agora; o que só você destrava está acima, no terminal.';
146
170
  /** A linha das perguntas prontas, uma so para os dois canais, pelo mesmo motivo da de baixo. */
147
171
  function linhaDasPerguntas(n) {
148
172
  return `Perguntas para você: ${n}`;
@@ -192,7 +216,7 @@ function abertura(r, codigo, marca, resposta) {
192
216
  return r.pergunta ? [`${marca}${r.pergunta}`, ...linhaDeResposta(codigo).map(l => `${resposta}${l}`), ''] : [];
193
217
  }
194
218
  function fecho(r) {
195
- return r.pergunta ? [] : ['', exports.SEM_PERGUNTA_NO_RESUMO];
219
+ return r.pergunta ? [] : ['', r.impedimentosDoDono?.length ? exports.SEM_PERGUNTA_COM_IMPEDIMENTO : exports.SEM_PERGUNTA_NO_RESUMO];
196
220
  }
197
221
  function textoTelegram(r, codigo) {
198
222
  const quando = (0, horario_1.formatarDataHoraRotulada)(r.consultadoEm, { agora: r.consultadoEm });
@@ -209,6 +233,7 @@ function textoTelegram(r, codigo) {
209
233
  ...(r.tecnicos ? [`🔧 ${linhaDosTecnicos(r)}`] : []),
210
234
  ...(r.consertos ? [`🔧 ${linhaDosConsertos(r.consertos)}`] : []),
211
235
  ...linhaDoHitlParado(r).map(l => `⏳ ${l}`),
236
+ ...(r.impedimentosDoDono?.length ? ['', ...linhasDosImpedimentos(r, '🔑 ', ' ')] : []),
212
237
  ...(r.paradosNoCondutor?.length ? ['', ...linhasDoCondutor(r, '🚧 ', ' ')] : []),
213
238
  ...(r.acumuladas ? ['', `📥 ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
214
239
  ...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '🧭 ', ' ')] : []),
@@ -241,6 +266,7 @@ function textoTerminal(r, codigo) {
241
266
  ...(r.tecnicos ? ['', ` ${linhaDosTecnicos(r)}`] : []),
242
267
  ...(r.consertos ? ['', ` ${linhaDosConsertos(r.consertos)}`] : []),
243
268
  ...linhaDoHitlParado(r).flatMap(l => ['', ` ${l}`]),
269
+ ...(r.impedimentosDoDono?.length ? ['', ...linhasDosImpedimentos(r, ' ', ' ')] : []),
244
270
  ...(r.paradosNoCondutor?.length ? ['', ...linhasDoCondutor(r, ' ', ' ')] : []),
245
271
  ...(r.acumuladas ? ['', ` ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
246
272
  ...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '', ' ').map(l => l.startsWith(' ') ? l : ` ${l}`)] : []),
@@ -42,6 +42,7 @@ const hitl_contract_1 = require("./hitl-contract");
42
42
  const horario_1 = require("./horario");
43
43
  const ledger_1 = require("./ledger");
44
44
  const thread_1 = require("./thread");
45
+ const locale_1 = require("./locale");
45
46
  /** A meta da RM-057: mediana de resposta abaixo de 5 minutos. */
46
47
  exports.META_DA_MEDIANA_DE_RESPOSTA_MS = 5 * 60_000;
47
48
  const RESPOSTAS = ['human_gate', 'session_answered'];
@@ -178,13 +179,12 @@ const emMinutos = (ms) => `${(ms / 60_000).toFixed(1)} min`;
178
179
  function textoDaMediana(h) {
179
180
  const s = h.seteDias;
180
181
  if (s.medianaRespostaMs === null)
181
- return 'mediana de 7 dias sem resposta medida';
182
- return `mediana de 7 dias ${emMinutos(s.medianaRespostaMs)} (${s.dentroDaMeta ? 'dentro' : 'acima'} da meta de 5 min, ` +
183
- `${s.respondidos} resposta(s))`;
182
+ return (0, locale_1.msg)().pulse.medianaSemResposta;
183
+ return (0, locale_1.msg)().pulse.mediana(emMinutos(s.medianaRespostaMs), s.dentroDaMeta, s.respondidos);
184
184
  }
185
185
  /** "<thread> (<fase>) parada ha 12min, desde 01:02": a hora no fuso do dono (RM-035). */
186
186
  function linhaDaPerguntaParada(p, opcoes) {
187
- return `${p.thread} (${p.fase}) parada há ${(0, horario_1.duracaoRelativa)(p.paradaHaMin)}, desde ${(0, horario_1.formatarHora)(p.desdeEm, opcoes)}`;
187
+ return (0, locale_1.msg)().pulse.perguntaParada(p.thread, p.fase, (0, horario_1.duracaoRelativa)(p.paradaHaMin), (0, horario_1.formatarHora)(p.desdeEm, opcoes));
188
188
  }
189
189
  /**
190
190
  * A linha unica do resumo do pulse, so quando passou da meta: quantas abertas, a mais antiga e a
package/dist/hitl.js CHANGED
@@ -88,6 +88,8 @@ const horario_1 = require("./horario");
88
88
  Object.defineProperty(exports, "duracaoCurta", { enumerable: true, get: function () { return horario_1.duracaoCurta; } });
89
89
  const hitl_estado_1 = require("./hitl-estado");
90
90
  const redacao_url_1 = require("./redacao-url");
91
+ const locale_1 = require("./locale");
92
+ const locale_pulse_1 = require("./locale-pulse");
91
93
  /** Limite de atencao padrao, em minutos, igual ao do monitor de orquestracao. */
92
94
  exports.ATENCAO_PADRAO_MIN = 30;
93
95
  function redigirSegredos(texto) {
@@ -110,28 +112,29 @@ function contextoSeguro(texto, linhas = 20) {
110
112
  * `stopped` NAO pede humano: a sessao foi parada por decisao de alguem. `failed` pede,
111
113
  * porque alguem precisa decidir entre retomar, corrigir ou abandonar.
112
114
  */
115
+ // RM-061 (F2b): o detalhe de cada estado tem a fonte no catalogo pt-BR (`locale-pulse.ts`), com o ingles ao lado.
113
116
  const REGRA_POR_ESTADO = {
114
117
  blocked: {
115
118
  classe: 'hitl',
116
119
  precisaDeHumano: true,
117
- detalhe: 'sessao parada esperando acao humana (prompt, permissao ou credencial)',
120
+ detalhe: locale_pulse_1.PULSE_PT_BR.sessao.blocked,
118
121
  },
119
122
  failed: {
120
123
  classe: 'falha',
121
124
  precisaDeHumano: true,
122
- detalhe: 'sessao terminou em erro: alguem decide entre retomar, corrigir ou abandonar',
125
+ detalhe: locale_pulse_1.PULSE_PT_BR.sessao.failed,
123
126
  },
124
- working: { classe: 'trabalhando', precisaDeHumano: false, detalhe: 'sessao em execucao' },
125
- done: { classe: 'concluida', precisaDeHumano: false, detalhe: 'sessao concluiu' },
127
+ working: { classe: 'trabalhando', precisaDeHumano: false, detalhe: locale_pulse_1.PULSE_PT_BR.sessao.working },
128
+ done: { classe: 'concluida', precisaDeHumano: false, detalhe: locale_pulse_1.PULSE_PT_BR.sessao.done },
126
129
  stopped: {
127
130
  classe: 'interrompida',
128
131
  precisaDeHumano: false,
129
- detalhe: 'sessao parada por decisao humana (`claude stop`)',
132
+ detalhe: locale_pulse_1.PULSE_PT_BR.sessao.stopped,
130
133
  },
131
134
  idle: {
132
135
  classe: 'interativa',
133
136
  precisaDeHumano: false,
134
- detalhe: 'sessao interativa ociosa, conduzida por um humano no terminal',
137
+ detalhe: locale_pulse_1.PULSE_PT_BR.sessao.idle,
135
138
  },
136
139
  };
137
140
  /** Estado cru da sessao: `state` manda, `status` e o fallback historico. */
@@ -142,14 +145,13 @@ function estadoBruto(s) {
142
145
  /** Classifica o estado cru do runtime. Estado que nao esta no mapa avisa a mais. */
143
146
  function classificarEstado(estado) {
144
147
  const regra = REGRA_POR_ESTADO[estado];
148
+ // RM-061 (F2b, G2): o detalhe vem do catalogo (o mesmo pt-BR da tabela) e o pulse o remonta no locale.
145
149
  if (regra)
146
- return regra;
150
+ return { ...regra, detalhe: (0, locale_1.textoDoCatalogo)((m) => m.pulse.sessao[estado] ?? regra.detalhe) };
147
151
  return {
148
152
  classe: 'desconhecida',
149
153
  precisaDeHumano: true,
150
- detalhe: estado === ''
151
- ? 'runtime nao informou estado desta sessao: tratada como possivel parada'
152
- : `estado "${estado}" nao esta no catalogo do ork: tratado como possivel parada`,
154
+ detalhe: (0, locale_1.textoDoCatalogo)((m) => estado === '' ? m.pulse.sessaoSemEstado : m.pulse.sessaoEstadoForaDoCatalogo(estado)),
153
155
  };
154
156
  }
155
157
  /** Resposta do `claude logs` quando o job da sessao ja saiu do runtime. */
@@ -279,28 +281,22 @@ function lerPerguntaDaTela(textoLimpo) {
279
281
  * inventa opcao que a sessao nao deu.
280
282
  */
281
283
  function recomendarPara(classe, tipo, id) {
282
- if (classe === 'abandonada') {
283
- return ('nao responda: o job ja saiu. Confira o que ficou na worktree da sessao e ' +
284
- 'redespache so o resto do trabalho.');
285
- }
286
- if (classe === 'falha') {
287
- return `leia \`ork sessions logs ${id}\` e decida entre retomar, corrigir a causa ou abandonar a sessao.`;
288
- }
289
- if (classe === 'desconhecida') {
290
- return `estado fora do catalogo: confirme no \`ork sessions logs ${id}\` se a sessao esta mesmo parada.`;
291
- }
292
- switch (tipo) {
293
- case 'hitl.credencial':
294
- return (`entregue o segredo agora (\`claude attach ${id}\`) para nao segurar a entrega; ` +
295
- 'se este mesmo bloqueio ja se repetiu, troque por credencial de automacao e ele nao volta.');
296
- case 'hitl.permissao':
297
- return (`responda o prompt (\`claude attach ${id}\`); se o comando e rotineiro nesse projeto, ` +
298
- 'coloque-o na allowlist de permissoes para nao parar de novo pelo mesmo motivo.');
299
- case 'hitl.pergunta':
300
- return `abra a sessao (\`claude attach ${id}\`) e escolha entre as opcoes que ela listou.`;
301
- default:
302
- return `veja o que a sessao pede com \`ork sessions logs ${id}\` antes de responder.`;
303
- }
284
+ // RM-061 (F2b, G2): nasce em pt-BR pelo catalogo (o --json guarda) e o pulse a remonta no locale.
285
+ return (0, locale_1.textoDoCatalogo)((m) => {
286
+ const r = m.pulse.recomendacaoDoRadar;
287
+ if (classe === 'abandonada')
288
+ return r.abandonada;
289
+ if (classe === 'falha')
290
+ return r.falha(id);
291
+ if (classe === 'desconhecida')
292
+ return r.desconhecida(id);
293
+ switch (tipo) {
294
+ case 'hitl.credencial': return r.credencial(id);
295
+ case 'hitl.permissao': return r.permissao(id);
296
+ case 'hitl.pergunta': return r.pergunta(id);
297
+ default: return r.outra(id);
298
+ }
299
+ });
304
300
  }
305
301
  /** Minutos inteiros entre um epoch em ms e um carimbo ISO (nunca negativo). */
306
302
  function idadeEmMinutos(inicioMs, ateEm) {
@@ -440,7 +436,7 @@ function varrerSessoes(opcoes = {}) {
440
436
  }
441
437
  const consultaClaude = opcoes.consulta ?? (adapter.disponivel()
442
438
  ? adapter.consultarSessoes(opcoes.soDaRaiz && raiz ? raiz : undefined, true)
443
- : { ok: false, sessoes: [], detalhe: 'runtime adapter indisponivel nesta maquina (`claude` fora do PATH)' });
439
+ : { ok: false, sessoes: [], detalhe: (0, locale_1.textoDoCatalogo)((m) => m.pulse.adapterIndisponivel) });
444
440
  const consulta = { ...consultaClaude, ok: consultaClaude.ok || nativas.size > 0,
445
441
  sessoes: [...consultaClaude.sessoes, ...nativas.values()] };
446
442
  if (!consulta.ok) {
package/dist/horario.js CHANGED
@@ -34,6 +34,7 @@ exports.formatarPrazo = formatarPrazo;
34
34
  exports.formatarDesde = formatarDesde;
35
35
  exports.localizarTexto = localizarTexto;
36
36
  exports.localizarTextoRotulado = localizarTextoRotulado;
37
+ const locale_1 = require("./locale");
37
38
  exports.CHAVE_DO_FUSO = 'owner.timezone';
38
39
  /** Único fuso com nome próprio no rótulo; exemplo da documentação e do `ork init`. */
39
40
  exports.FUSO_DE_BRASILIA = 'America/Sao_Paulo';
@@ -194,11 +195,11 @@ function duracaoCurta(minutos) {
194
195
  }
195
196
  /** Duração até ou desde agora: abaixo de 1 min diz `menos de 1 min`, nunca `0min`. */
196
197
  function duracaoRelativa(minutos) {
197
- return minutos < 1 ? 'menos de 1 min' : duracaoCurta(minutos);
198
+ return minutos < 1 ? (0, locale_1.msg)().horario.menosDeUmMin : duracaoCurta(minutos);
198
199
  }
199
- function sigla(fuso, ms) {
200
+ function sigla(fuso, ms, locale = (0, locale_1.localeAtivo)()) {
200
201
  try {
201
- return new Intl.DateTimeFormat('pt-BR', { timeZone: fuso, timeZoneName: 'short' })
202
+ return new Intl.DateTimeFormat(locale === 'en' ? 'en-US' : 'pt-BR', { timeZone: fuso, timeZoneName: 'short' })
202
203
  .formatToParts(new Date(ms)).find(p => p.type === 'timeZoneName')?.value ?? '';
203
204
  }
204
205
  catch {
@@ -208,7 +209,7 @@ function sigla(fuso, ms) {
208
209
  /** Como o fuso aparece entre parênteses: `horário de Brasília`, `UTC`, `Europe/Lisbon, GMT+1`. */
209
210
  function rotuloDoFuso(fuso = fusoDoDono().fuso, quando) {
210
211
  if (fuso === exports.FUSO_DE_BRASILIA)
211
- return 'horário de Brasília';
212
+ return (0, locale_1.msg)().horario.brasilia;
212
213
  if (fuso === 'UTC')
213
214
  return 'UTC';
214
215
  const ms = instante(quando);
@@ -218,16 +219,22 @@ function rotuloDoFuso(fuso = fusoDoDono().fuso, quando) {
218
219
  /** Legenda de mensagem com vários horários: `Horários de Brasília.` */
219
220
  function legendaDoFuso(fuso = fusoDoDono().fuso, quando) {
220
221
  if (fuso === exports.FUSO_DE_BRASILIA)
221
- return 'Horários de Brasília.';
222
- return `Horários em ${rotuloDoFuso(fuso, quando)}.`;
222
+ return (0, locale_1.msg)().horario.legendaBrasilia;
223
+ return (0, locale_1.msg)().horario.legenda(rotuloDoFuso(fuso, quando));
223
224
  }
224
- /** `19/09 15:16`; `19/09/2025 15:16` quando o ano não é o corrente do dono. Inválido volta como veio. */
225
+ /**
226
+ * `19/09 15:16`; `19/09/2025 15:16` quando o ano não é o corrente do dono. Em `en`, sempre
227
+ * `2026-09-19 15:16` (EN6). Inválido volta como veio.
228
+ */
225
229
  function formatarDataHora(quando, opcoes = {}) {
226
230
  const ms = instante(quando ?? null);
227
231
  if (!Number.isFinite(ms))
228
232
  return quando === null || quando === undefined ? '' : String(quando);
229
233
  const fuso = opcoes.fuso ?? fusoDoDono().fuso;
230
234
  const p = partesLocais(ms, fuso), a = partesLocais(instante(opcoes.agora), fuso);
235
+ if ((opcoes.locale ?? (0, locale_1.localeAtivo)()) === 'en') {
236
+ return `${p.ano}-${p.mes}-${p.dia} ${p.hora}:${p.minuto}` + (opcoes.segundos ? `:${p.segundo}` : '');
237
+ }
231
238
  return `${p.dia}/${p.mes}${p.ano !== a.ano ? `/${p.ano}` : ''} ${p.hora}:${p.minuto}` +
232
239
  (opcoes.segundos ? `:${p.segundo}` : '');
233
240
  }
package/dist/hosts.js CHANGED
@@ -354,7 +354,7 @@ function instalarAdaptador(host, opcoes = {}) {
354
354
  let planoExperiencia = null;
355
355
  if (preferencias && hostComBloco && !avisoExperiencia) {
356
356
  try {
357
- planoExperiencia = (0, experiencia_instalacao_1.planejarExperiencia)(projeto, host, preferencias.experience ? diretorioExperiencia : null);
357
+ planoExperiencia = (0, experiencia_instalacao_1.planejarExperiencia)(projeto, host, preferencias.experience ? diretorioExperiencia : null, opcoes.locale);
358
358
  }
359
359
  catch (e) {
360
360
  const motivo = e.message;
@@ -421,6 +421,7 @@ function instalarAdaptador(host, opcoes = {}) {
421
421
  estado,
422
422
  });
423
423
  }
424
+ const removidos = sobrasDeMods(destino, recibo, arquivos);
424
425
  const conflitos = arquivos.filter((a) => a.estado === 'sobrescrito');
425
426
  /**
426
427
  * I-43 (D5): a divergencia continua sendo DETECTADA; ela deixa de BARRAR quando e
@@ -458,6 +459,7 @@ function instalarAdaptador(host, opcoes = {}) {
458
459
  ...(preferencias || avisoExperiencia ? { experiencia: { ativa: host !== 'openclaw' && !!preferencias?.experience && !avisoExperiencia,
459
460
  arquivos: planoExperiencia?.mudancas.map(m => path.relative(planoExperiencia.projeto, m.arquivo)) ?? [], skill: preferencias?.skill ?? '',
460
461
  ...(avisoExperiencia ? { aviso: avisoExperiencia } : {}) } } : {}),
462
+ ...(removidos.length ? { removidos } : {}),
461
463
  };
462
464
  if (opcoes.dryRun === true || barrado)
463
465
  return resultado;
@@ -477,6 +479,17 @@ function instalarAdaptador(host, opcoes = {}) {
477
479
  if (arquivo.executavel)
478
480
  fs.chmodSync(arquivo.destino, 0o755);
479
481
  }
482
+ // RM-059 (T0.7, A1 do CHECK): o que saiu do catalogo em `mods/` sai da copia, com a pasta que ficar vazia.
483
+ for (const relativo of removidos) {
484
+ const alvo = path.join(destino, relativo);
485
+ fs.unlinkSync(alvo);
486
+ const raizDoMod = path.join(destino, ...relativo.split('/').slice(0, 2));
487
+ for (let dir = path.dirname(alvo); dir.startsWith(raizDoMod); dir = path.dirname(dir)) {
488
+ if (fs.readdirSync(dir).length)
489
+ break;
490
+ fs.rmdirSync(dir);
491
+ }
492
+ }
480
493
  // O recibo: com ele da para saber, depois, se alguem editou a copia instalada em vez do
481
494
  // catalogo. Sem ele a instalacao e um monte de arquivo sem procedencia.
482
495
  fs.writeFileSync(path.join(destino, 'INSTALADO.json'), JSON.stringify({
@@ -497,6 +510,41 @@ function instalarAdaptador(host, opcoes = {}) {
497
510
  (0, experiencia_instalacao_1.aplicarExperiencia)(planoExperiencia);
498
511
  return resultado;
499
512
  }
513
+ /**
514
+ * RM-059 (T0.7, A1 do CHECK): os arquivos de `mods/` do recibo anterior que nao estao mais no catalogo e que a
515
+ * copia guarda como foram instalados (sha256 do recibo), os de cada mod e os comuns da raiz de `mods/` (N2 da
516
+ * rodada 2). Arquivo editado, link ou caminho fora do destino fica: o despacho continua recusando com o motivo,
517
+ * e a decisao e de quem editou.
518
+ */
519
+ function sobrasDeMods(destino, recibo, arquivos) {
520
+ if (!recibo)
521
+ return [];
522
+ const novos = new Set(arquivos.map((a) => a.relativo));
523
+ let base;
524
+ try {
525
+ base = fs.realpathSync(destino);
526
+ }
527
+ catch {
528
+ return [];
529
+ }
530
+ return recibo.arquivos.flatMap((a) => {
531
+ const rel = typeof a.arquivo === 'string' ? a.arquivo : '';
532
+ if (!/^mods\/[^/]+(?:$|\/.)/.test(rel) || novos.has(rel) || path.isAbsolute(rel) || rel.split('/').includes('..'))
533
+ return [];
534
+ const alvo = path.join(destino, rel);
535
+ const st = fs.lstatSync(alvo, { throwIfNoEntry: false });
536
+ if (!st || !st.isFile() || st.nlink !== 1)
537
+ return [];
538
+ try {
539
+ if (!fs.realpathSync(path.dirname(alvo)).startsWith(base + path.sep))
540
+ return [];
541
+ }
542
+ catch {
543
+ return [];
544
+ }
545
+ return sha256(fs.readFileSync(alvo)) === a.sha256 ? [rel] : [];
546
+ });
547
+ }
500
548
  /** Caminho real do maior ancestral existente, com o resto como está: o destino pode não existir ainda. */
501
549
  function caminhoReal(alvo) {
502
550
  let existente = path.resolve(alvo);
@@ -592,6 +640,9 @@ function textoDaInstalacao(r) {
592
640
  const porque = a.divergencia ? ` (${a.divergencia})` : '';
593
641
  linhas.push(` ${marca} ${a.relativo}${porque}`);
594
642
  }
643
+ // RM-059 (T0.7): arquivo de mod que saiu do catalogo sai da copia, para o despacho nao recusar o mod.
644
+ for (const rel of r.removidos ?? [])
645
+ linhas.push(` - ${rel} (saiu do catalogo${r.dryRun ? ', seria removido' : ', removido'})`);
595
646
  // I-43 (D5): o `--dir` na raiz avisa, em vez de reportar "tudo novo" com saida 0.
596
647
  if (r.reciboVizinho) {
597
648
  linhas.push('');
@@ -79,6 +79,17 @@ function ehEsperaDoDono(e, tipoGateBloqueado = 'gate_blocked') {
79
79
  function citar(caminho) {
80
80
  return /^[A-Za-z0-9_./:@+-]+$/.test(caminho) ? caminho : `'${caminho.replace(/'/g, `'\\''`)}'`;
81
81
  }
82
+ /**
83
+ * RM-055 (continuacao, D4): frases que dizem "Workspace not trusted" mas que aceitar a confianca NAO
84
+ * resolve. No claude 2.1.296 o veredito do `--bg` tem tres saidas: o diretorio que nao existe mais no
85
+ * disco, o diretorio home (que so confia uma sessao por vez) e a recusa de confianca de verdade. As duas
86
+ * primeiras ficam no caminho generico (`runtime.unavailable`), com o stderr gravado no despacho falho:
87
+ * mandar o dono rodar `cd <dir> && claude` ali nao destrava nada.
88
+ */
89
+ const FRASES_QUE_NAO_SAO_DO_DONO = [
90
+ /could not be resolved on disk/i,
91
+ /home directory is trusted one session at a time/i,
92
+ ];
82
93
  const FRASES_DE_CONFIANCA = [
83
94
  /workspace not trusted/i,
84
95
  /accept the trust (?:prompt|dialog)/i,
@@ -86,10 +97,32 @@ const FRASES_DE_CONFIANCA = [
86
97
  /(?:folder|directory|workspace) (?:is )?not trusted/i,
87
98
  ];
88
99
  const FRASES_DE_CONSENTIMENTO = [
89
- /accept (?:the )?(?:updated |new )?(?:terms|consumer terms|privacy policy|usage policy)/i,
90
- /(?:terms|policy) (?:have|has) (?:been )?updated[^\n]*accept/i,
91
- /consent (?:is )?required/i,
100
+ // claude 2.1.296, modo print: o aviso que bloqueia e sai com 1. O aviso de carencia ("will take effect
101
+ // ... Run `claude` to review the updated terms") nao bloqueia e nao casa aqui.
102
+ { regra: /\[ACTION REQUIRED\][^\n]*(?:terms|policy)/i, aceita: 'revisar e aceitar os termos novos',
103
+ feito: 'aceite os termos e saia' },
104
+ // claude 2.1.296: o portao do `--bg` que vem antes da confianca (gateCause "disclaimer").
105
+ { regra: /requires accepting the disclaimer first/i, aceita: 'aceitar o aviso do modo sem permissões (bypass)',
106
+ feito: 'aceite o aviso e saia' },
107
+ { regra: /requires opting in first/i, aceita: 'ativar o modo auto uma vez', feito: 'confirme o modo auto e saia' },
108
+ // Fatia 1 (02/10/2026): frases genericas de termos novos.
109
+ { regra: /accept (?:the )?(?:updated |new )?(?:terms|consumer terms|privacy policy|usage policy)/i,
110
+ aceita: 'aceitar termos novos', feito: 'aceite os termos e saia' },
111
+ { regra: /(?:terms|policy) (?:have|has) (?:been )?updated[^\n]*accept/i, aceita: 'aceitar termos novos',
112
+ feito: 'aceite os termos e saia' },
113
+ { regra: /consent (?:is )?required/i, aceita: 'aceitar termos novos', feito: 'aceite os termos e saia' },
92
114
  ];
115
+ /** A forma fechada de um comando que o runtime manda rodar: o binario e opcoes `--nome` com valor simples. */
116
+ const FORMA_DO_COMANDO = /^(?:claude|codex)(?: --[a-z][a-z-]*(?: [a-z][a-z0-9-]*)?)*$/;
117
+ /**
118
+ * RM-055 (continuacao, D4): o comando que a propria frase do runtime manda rodar ("Run `claude
119
+ * --permission-mode auto` once interactively"). So na forma fechada e so do binario do runtime: a saida
120
+ * do runtime nunca chega ao dono como comando arbitrario. Fora disso, o binario puro.
121
+ */
122
+ function comandoDaFrase(trecho, bin) {
123
+ const pedido = /\brun `([^`]{1,120})`/i.exec(trecho)?.[1].replace(/\s+/g, ' ').trim();
124
+ return pedido && FORMA_DO_COMANDO.test(pedido) && pedido.split(' ')[0] === bin ? pedido : bin;
125
+ }
93
126
  function trechoDa(texto, regra) {
94
127
  const linha = texto.split(/\r?\n/).find(l => regra.test(l)) ?? texto;
95
128
  return linha.replace(/\s+/g, ' ').trim().slice(0, 240);
@@ -103,6 +136,8 @@ function binarioDo(runtime) {
103
136
  */
104
137
  function classificarImpedimento(saida, ctx) {
105
138
  const texto = saida ?? '';
139
+ if (FRASES_QUE_NAO_SAO_DO_DONO.some(r => r.test(texto)))
140
+ return null;
106
141
  const bin = binarioDo(ctx.runtime);
107
142
  const depois = `depois, \`ork retry run ${ctx.thread}\` re-despacha ${ctx.fase} com o mesmo prompt gravado`;
108
143
  const confianca = FRASES_DE_CONFIANCA.find(r => r.test(texto));
@@ -115,23 +150,77 @@ function classificarImpedimento(saida, ctx) {
115
150
  trecho: trechoDa(texto, confianca),
116
151
  };
117
152
  }
118
- const consentimento = FRASES_DE_CONSENTIMENTO.find(r => r.test(texto));
153
+ const consentimento = FRASES_DE_CONSENTIMENTO.find(c => c.regra.test(texto));
119
154
  if (consentimento) {
155
+ const trecho = trechoDa(texto, consentimento.regra);
120
156
  return {
121
157
  motivo: 'runtime.consent-pending',
122
- trava: `o ${bin} espera você aceitar termos novos e recusou o despacho de ${ctx.fase}`,
123
- comando: bin,
124
- depois: `aceite os termos e saia; ${depois}`,
125
- trecho: trechoDa(texto, consentimento),
158
+ trava: `o ${bin} espera você ${consentimento.aceita} e recusou o despacho de ${ctx.fase}`,
159
+ comando: comandoDaFrase(trecho, bin),
160
+ depois: `${consentimento.feito}; ${depois}`,
161
+ trecho,
126
162
  };
127
163
  }
128
164
  return null;
129
165
  }
166
+ /** O caminho como o claude o compara: o real, sem link; o resolvido quando ele nao existe no disco. */
167
+ function caminhoReal(caminho) {
168
+ try {
169
+ return fs.realpathSync.native(caminho);
170
+ }
171
+ catch {
172
+ return path.resolve(caminho);
173
+ }
174
+ }
175
+ /** A raiz git do diretorio: o ancestral mais proximo, ele incluido, com `.git` (arquivo ou diretorio). */
176
+ function raizGit(dir) {
177
+ for (let d = dir;; d = path.dirname(d)) {
178
+ try {
179
+ const st = fs.lstatSync(path.join(d, '.git'));
180
+ if (st.isFile() || st.isDirectory())
181
+ return d;
182
+ }
183
+ catch { /* sem `.git` aqui: sobe */ }
184
+ if (path.dirname(d) === d)
185
+ return null;
186
+ }
187
+ }
188
+ /**
189
+ * A raiz canonica de uma raiz git, conferida como o claude 2.1.296 confere: numa worktree vinculada (o
190
+ * arquivo `.git` aponta `gitdir:` para `<comum>/worktrees/<nome>`, que tem `commondir` e um `gitdir` de
191
+ * volta para `<raiz>/.git`), e o repositorio principal, o diretorio de cima do `<comum>` chamado `.git`.
192
+ * Qualquer conferencia que falha devolve a propria raiz, e um repositorio comum (`.git` diretorio) tambem.
193
+ */
194
+ function raizCanonica(raiz) {
195
+ try {
196
+ const conteudo = fs.readFileSync(path.join(raiz, '.git'), 'utf8').trim();
197
+ if (!conteudo.startsWith('gitdir:'))
198
+ return raiz;
199
+ const gitdir = path.resolve(raiz, conteudo.slice('gitdir:'.length).trim());
200
+ const comum = path.resolve(gitdir, fs.readFileSync(path.join(gitdir, 'commondir'), 'utf8').trim());
201
+ if (path.dirname(gitdir) !== path.join(comum, 'worktrees'))
202
+ return raiz;
203
+ const deVolta = path.resolve(gitdir, fs.readFileSync(path.join(gitdir, 'gitdir'), 'utf8').trim());
204
+ if (caminhoReal(deVolta) !== path.join(caminhoReal(raiz), '.git'))
205
+ return raiz;
206
+ if (path.basename(comum) !== '.git')
207
+ return fs.existsSync(path.join(comum, '.git')) ? raiz : comum;
208
+ return path.dirname(comum);
209
+ }
210
+ catch {
211
+ return raiz;
212
+ }
213
+ }
130
214
  /**
131
215
  * O impedimento ja foi resolvido? So o de confianca do diretorio tem prova local: o `claude` grava
132
- * `hasTrustDialogAccepted` por projeto no `.claude.json` da conta (o do perfil, com `CLAUDE_CONFIG_DIR`),
133
- * e a confianca de um diretorio vale para os de baixo. `null` e "nao sei": o retry despacha e o runtime
134
- * decide; se recusar de novo, a mesma pausa volta.
216
+ * `hasTrustDialogAccepted` por projeto no `.claude.json` da conta (o do perfil, com `CLAUDE_CONFIG_DIR`).
217
+ * `null` e "nao sei": o retry despacha e o runtime decide; se recusar de novo, a mesma pausa volta.
218
+ *
219
+ * RM-055 (continuacao, D1): a regra e a que o claude 2.1.296 aplica no `--bg`, medida com worktrees git
220
+ * reais: vale a confianca do proprio diretorio e dos de cima ate a raiz git, inclusive, e a da raiz
221
+ * canonica de uma worktree vinculada (o repositorio principal), more a worktree dentro ou fora dele. Acima
222
+ * da raiz git nada vale: com so o diretorio de cima do repositorio confiado, o claude recusa a worktree.
223
+ * Fora de git, qualquer diretorio de cima vale, como no claude.
135
224
  */
136
225
  function impedimentoResolvido(motivo, ctx) {
137
226
  if (motivo !== 'runtime.workspace-untrusted' || ctx.runtime !== 'claude-bg')
@@ -149,10 +238,15 @@ function impedimentoResolvido(motivo, ctx) {
149
238
  catch {
150
239
  return null;
151
240
  }
152
- for (let dir = path.resolve(ctx.cwd);; dir = path.dirname(dir)) {
153
- if (projetos[dir]?.hasTrustDialogAccepted === true)
241
+ const confia = (dir) => projetos[dir]?.hasTrustDialogAccepted === true;
242
+ const cwd = caminhoReal(ctx.cwd);
243
+ const raiz = raizGit(cwd);
244
+ if (raiz && confia(raizCanonica(raiz)))
245
+ return true;
246
+ for (let dir = cwd;; dir = path.dirname(dir)) {
247
+ if (confia(dir))
154
248
  return true;
155
- if (path.dirname(dir) === dir)
249
+ if (dir === raiz || path.dirname(dir) === dir)
156
250
  return false;
157
251
  }
158
252
  }