@orkastery/cli 0.5.1 → 0.5.3

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 (110) hide show
  1. package/adapters/claude-code/agents/ork-master.md +2 -0
  2. package/adapters/claude-code/commands/master.md +8 -0
  3. package/adapters/claude-code/commands/ork.md +9 -0
  4. package/adapters/codex/skills/ork/SKILL.md +18 -3
  5. package/adapters/hermes/README.md +1 -0
  6. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  7. package/adapters/hermes/hermes.plugin.json +1 -0
  8. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  9. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
  10. package/adapters/openclaw/README.md +24 -4
  11. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  12. package/adapters/openclaw/dist/index.js +46 -11
  13. package/adapters/openclaw/openclaw.plugin.json +5 -1
  14. package/adapters/openclaw/package.json +2 -2
  15. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  16. package/adapters/openclaw/src/index.ts +49 -11
  17. package/assets/orkmind_bridge.py +117 -9
  18. package/dist/adapters/claude-bg.js +39 -5
  19. package/dist/adapters/codex.js +4 -3
  20. package/dist/agents-md.js +1 -0
  21. package/dist/auditrun.js +5 -1
  22. package/dist/board.js +16 -14
  23. package/dist/branch-de-estado.js +66 -10
  24. package/dist/busca-semantica.js +52 -12
  25. package/dist/canarios-rm057.js +149 -0
  26. package/dist/canarios.js +2 -0
  27. package/dist/ci.js +8 -1
  28. package/dist/claims.js +39 -1
  29. package/dist/company-brain-context.js +158 -22
  30. package/dist/docs.js +48 -9
  31. package/dist/doctor-pulse-cron.js +194 -0
  32. package/dist/doctor-rede.js +149 -0
  33. package/dist/doctor.js +89 -9
  34. package/dist/entrega-pr.js +20 -8
  35. package/dist/esgotamentos.js +181 -0
  36. package/dist/experiencia.js +12 -2
  37. package/dist/fabrica-estado.js +14 -6
  38. package/dist/fabrica-publicar.js +6 -0
  39. package/dist/fechamento.js +20 -66
  40. package/dist/forja.js +31 -7
  41. package/dist/gates.js +5 -1
  42. package/dist/hitl-contract.js +59 -4
  43. package/dist/hitl-curto.js +11 -6
  44. package/dist/hitl-gates.js +5 -0
  45. package/dist/hitl-lote.js +5 -2
  46. package/dist/hitl-resumo.js +9 -0
  47. package/dist/hitl-tempo-parado.js +201 -0
  48. package/dist/hitl-texto-livre.js +5 -5
  49. package/dist/horario.js +27 -0
  50. package/dist/hosts.js +52 -10
  51. package/dist/impedimento.js +205 -0
  52. package/dist/index.js +391 -74
  53. package/dist/indice-vetorial.js +87 -10
  54. package/dist/indice.js +3 -1
  55. package/dist/init.js +17 -0
  56. package/dist/integracoes-locais.js +2 -0
  57. package/dist/intelligence-graph-cli.js +91 -8
  58. package/dist/intelligence-graph-contexto.js +215 -0
  59. package/dist/intelligence-graph-parsers.js +6 -4
  60. package/dist/leases.js +664 -87
  61. package/dist/ledger-stats.js +15 -1
  62. package/dist/ledger.js +3 -0
  63. package/dist/licoes.js +1 -0
  64. package/dist/maestro-runtime.js +4 -1
  65. package/dist/manifest.js +20 -1
  66. package/dist/master-nota.js +14 -5
  67. package/dist/master.js +129 -12
  68. package/dist/mcp-artifacts.js +34 -3
  69. package/dist/mcp-git.js +20 -3
  70. package/dist/mcp-grafo-worker.js +5 -3
  71. package/dist/mcp-grafo.js +50 -4
  72. package/dist/mcp-server.js +14 -1
  73. package/dist/memoria.js +48 -6
  74. package/dist/network-roadmap.js +191 -16
  75. package/dist/ocupacao.js +11 -3
  76. package/dist/orkmind.js +90 -5
  77. package/dist/orquestracao.js +12 -10
  78. package/dist/parado-no-condutor.js +188 -32
  79. package/dist/phase.js +68 -15
  80. package/dist/policies.js +52 -1
  81. package/dist/portfolio.js +4 -1
  82. package/dist/projeto-alvo.js +5 -1
  83. package/dist/prompts.js +36 -1
  84. package/dist/prova-ativacao.js +75 -6
  85. package/dist/pulse-delivery.js +34 -2
  86. package/dist/pulse.js +49 -3
  87. package/dist/ratelimit.js +20 -4
  88. package/dist/recall.js +3 -1
  89. package/dist/rede-adesao.js +399 -0
  90. package/dist/rede-forja.js +289 -0
  91. package/dist/rede-projetos.js +239 -0
  92. package/dist/rede-status.js +336 -0
  93. package/dist/rede.js +980 -0
  94. package/dist/retry.js +69 -2
  95. package/dist/roadmap-status.js +23 -3
  96. package/dist/runtime-profiles.js +22 -1
  97. package/dist/saida-segura.js +58 -0
  98. package/dist/session-watcher-claude.js +113 -23
  99. package/dist/sessoes-contas.js +187 -0
  100. package/dist/sessoes-inventario.js +6 -18
  101. package/dist/sessoes.js +83 -2
  102. package/dist/ship.js +85 -19
  103. package/dist/thread.js +192 -13
  104. package/dist/worktree.js +5 -2
  105. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  106. package/package.json +7 -3
  107. package/references/definition-of-done.md +1 -1
  108. package/skills/README.md +1 -1
  109. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  110. package/skills/phases/master-metrics/SKILL.md +8 -3
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TEXTO_DO_ATO = exports.TETO_DE_LINHAS_DO_CORPO = exports.TETO_DE_LINHAS_DO_PEDIDO = exports.CONTRATO_PEDIDO_CURTO = void 0;
3
+ exports.TEXTO_DO_ATO = exports.TETO_DE_LINHAS_DO_CORPO = exports.TETO_DE_LINHAS_DO_PEDIDO = exports.SELO_DA_RECOMENDADA = exports.CONTRATO_PEDIDO_CURTO = void 0;
4
4
  exports.entradaDoPedido = entradaDoPedido;
5
5
  exports.montarPedidoCurto = montarPedidoCurto;
6
6
  exports.linhaDeResposta = linhaDeResposta;
@@ -23,7 +23,8 @@ exports.desdeDoPedido = desdeDoPedido;
23
23
  * trava o que esta parado, desde quando, e o que acontece sem resposta;
24
24
  * sem volta so quando responder autoriza um ato da lista congelada;
25
25
  * corpo no maximo `TETO_DE_LINHAS_DO_CORPO` linhas de evidencia;
26
- * alternativas de 2 a 4, uma linha cada, com a consequencia, e UMA recomendada com o porque;
26
+ * alternativas de 2 a 5, uma linha cada, com a consequencia, e UMA com o selo "Recomendação" e o
27
+ * porque (RM-057: o pedido que o proprio ork abre traz de 3 a 5);
27
28
  * responder a ultima linha, dizendo exatamente o que digitar.
28
29
  *
29
30
  * O detalhe profundo (artefato, claims, riscos, diff) fica a um pedido de distancia: a ultima
@@ -36,6 +37,8 @@ const hitl_1 = require("./hitl");
36
37
  const hitl_contract_1 = require("./hitl-contract");
37
38
  const horario_1 = require("./horario");
38
39
  exports.CONTRATO_PEDIDO_CURTO = 'ork.hitl-curto/v1';
40
+ /** RM-057: o selo que o dono procura na lista, igual em todo canal. */
41
+ exports.SELO_DA_RECOMENDADA = 'Recomendação';
39
42
  /** Nenhum pedido passa disto por padrao (RM-048, criterio c). */
40
43
  exports.TETO_DE_LINHAS_DO_PEDIDO = 15;
41
44
  /** Evidencia que cabe na pergunta curta; o resto vai no detalhe. */
@@ -80,11 +83,11 @@ function semResposta(e, agora) {
80
83
  }
81
84
  /**
82
85
  * Monta o pedido curto. Recusa, em vez de cortar em silencio, o que o contrato nao aceita: menos
83
- * de duas alternativas, mais de quatro, ou recomendada diferente de exatamente uma.
86
+ * de duas alternativas, mais de cinco, ou recomendada diferente de exatamente uma.
84
87
  */
85
88
  function montarPedidoCurto(e, opcoes) {
86
- if (e.alternativas.length < 2 || e.alternativas.length > 4)
87
- throw new Error('pedido curto: de 2 a 4 alternativas');
89
+ if (e.alternativas.length < 2 || e.alternativas.length > 5)
90
+ throw new Error('pedido curto: de 2 a 5 alternativas');
88
91
  if (e.alternativas.filter(a => a.recomendada).length !== 1)
89
92
  throw new Error('pedido curto: exatamente uma alternativa é recomendada');
90
93
  const onde = e.alvo?.tipo === 'session'
@@ -110,6 +113,8 @@ function linhaDeResposta(p, canal) {
110
113
  return null;
111
114
  if (r.tipo === 'texto')
112
115
  return `${canal === 'telegram' ? '↩️ ' : ''}Responda escrevendo o texto; ele é enviado como está.`;
116
+ if (r.tipo === 'terminal')
117
+ return `${canal === 'telegram' ? '↩️ ' : 'Responder: '}feito o comando, ${recomendada}) é \`${r.comando}\`.`;
113
118
  if (r.tipo === 'dialogo')
114
119
  return `${canal === 'telegram' ? '↩️ ' : ''}Responda selecionando a opção no diálogo; digitar a letra também vale.`;
115
120
  return canal === 'telegram'
@@ -132,7 +137,7 @@ function textoDoPedidoCurto(p, canal) {
132
137
  ...(p.semVolta ? [`${tg ? '🔒 ' : '! '}${p.semVolta}`] : []),
133
138
  ...p.corpo.map(l => `${tg ? '• ' : '- '}${l}`),
134
139
  ...p.alternativas.flatMap(a => [
135
- `${a.chave}) ${a.texto}${a.recomendada ? (tg ? ' ✅ recomendada' : ' [recomendada]') : ''}: ${a.consequencia}`,
140
+ `${a.chave}) ${a.texto}${a.recomendada ? (tg ? ` ✅ ${exports.SELO_DA_RECOMENDADA}` : ` [${exports.SELO_DA_RECOMENDADA}]`) : ''}: ${a.consequencia}`,
136
141
  ...(a.recomendada ? [` porquê: ${a.recomendada.porque}`] : []),
137
142
  ]),
138
143
  ];
@@ -215,6 +215,11 @@ function resolverCriterio(raiz, pedido) {
215
215
  }
216
216
  function registrarPedidoHitl(raiz, pedido) {
217
217
  (0, hitl_contract_1.validarPedidoHitl)(pedido);
218
+ // RM-057: o pedido que o ork abre ao dono e uma selecao de 3 a 5, com uma recomendada. O v1 e o
219
+ // contrato congelado que o nucleo nao emite mais por aqui (o gate sai em v2 desde a I-41); ele
220
+ // segue registravel so para os leitores do historico e para os testes que o reproduzem.
221
+ if ((0, hitl_contract_1.ehV2)(pedido))
222
+ (0, hitl_contract_1.exigirSelecaoDeConducao)(pedido);
218
223
  // R1, camada 1: um `decidido` cujo critério não resolve não chega a existir.
219
224
  if ((0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'decidido') {
220
225
  const { resolve, detalhe } = resolverCriterio(raiz, pedido);
package/dist/hitl-lote.js CHANGED
@@ -34,8 +34,11 @@ const hitl_curto_1 = require("./hitl-curto");
34
34
  exports.CONTRATO_LOTE = 'ork.hitl-lote/v1';
35
35
  /** De cinco em cinco, como o dono pediu. As que sobram continuam guardadas, nao somem. */
36
36
  exports.TETO_DE_PERGUNTAS_POR_LOTE = 5;
37
- /** De 2 a 4 alternativas, rotuladas em sequencia. Cinco nao cabem numa escolha rapida. */
38
- exports.LETRAS = ['a', 'b', 'c', 'd'];
37
+ /**
38
+ * De 2 a 5 alternativas, rotuladas em sequencia. RM-057: o pedido de conducao do ork traz de 3 a 5;
39
+ * o lote ainda le as de 2 do historico e as perguntas nativas das sessoes.
40
+ */
41
+ exports.LETRAS = ['a', 'b', 'c', 'd', 'e'];
39
42
  /**
40
43
  * O que acontece ao escolher, por acao. Mapa TOTAL sobre as acoes do pedido: acao nova quebra o
41
44
  * build aqui, que e o comportamento desejado. Uma alternativa sem consequencia dita e uma
@@ -29,6 +29,7 @@ exports.textoDoResumo = textoDoResumo;
29
29
  * mensagem do canal. Quem entrega chama a segunda.
30
30
  */
31
31
  const horario_1 = require("./horario");
32
+ const hitl_tempo_parado_1 = require("./hitl-tempo-parado");
32
33
  const decisao_autonoma_1 = require("./decisao-autonoma");
33
34
  const hitl_classificacao_1 = require("./hitl-classificacao");
34
35
  exports.CONTRATO_RESUMO = 'ork.hitl-resumo/v1';
@@ -59,6 +60,7 @@ function resumirHitl(itens, opcoes) {
59
60
  threadsTecnicas: [...new Set(tecnicos.map(i => i.thread).filter((t) => !!t))].sort(),
60
61
  ...(opcoes.paradosNoCondutor?.length ? { paradosNoCondutor: opcoes.paradosNoCondutor.map(p => ({ thread: p.thread, caso: p.caso,
61
62
  desdeEm: p.desdeEm, proximoPasso: p.proximoPasso })) } : {}),
63
+ ...(opcoes.hitlDeConducao?.acimaDaMeta ? { hitlDeConducao: opcoes.hitlDeConducao } : {}),
62
64
  };
63
65
  }
64
66
  /** Quantas linhas do condutor o resumo mostra antes de contar o resto. */
@@ -78,6 +80,11 @@ function linhasDoCondutor(r, marcador, recuo) {
78
80
  return [...mostradas.map(p => `${marcador}${linhaDoParadoNoCondutor(p, { agora: r.consultadoEm })}`),
79
81
  ...(sobra > 0 ? [`${recuo}e mais ${sobra}: ork pulse`] : [])];
80
82
  }
83
+ /** RM-057 (fatia 3): a linha do tempo parado por HITL de conducao, a mesma nos dois canais. */
84
+ function linhaDoHitlParado(r) {
85
+ const linha = (0, hitl_tempo_parado_1.linhaDoHitlAcimaDaMeta)(r.hitlDeConducao, { agora: r.consultadoEm });
86
+ return linha ? [linha] : [];
87
+ }
81
88
  /** RM-048 (D6): a linha do que e tecnico, igual nos dois canais. Nao pede nada ao dono. */
82
89
  function linhaDosTecnicos(r) {
83
90
  const nomes = r.threadsTecnicas.length ? ` (${nomearThreads(r.threadsTecnicas)})` : '';
@@ -201,6 +208,7 @@ function textoTelegram(r, codigo) {
201
208
  `❓ ${linhaDasPerguntas(r.prontas)}`,
202
209
  ...(r.tecnicos ? [`🔧 ${linhaDosTecnicos(r)}`] : []),
203
210
  ...(r.consertos ? [`🔧 ${linhaDosConsertos(r.consertos)}`] : []),
211
+ ...linhaDoHitlParado(r).map(l => `⏳ ${l}`),
204
212
  ...(r.paradosNoCondutor?.length ? ['', ...linhasDoCondutor(r, '🚧 ', ' ')] : []),
205
213
  ...(r.acumuladas ? ['', `📥 ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
206
214
  ...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '🧭 ', ' ')] : []),
@@ -232,6 +240,7 @@ function textoTerminal(r, codigo) {
232
240
  ? ['', ` threads travadas (${r.threadsBloqueadas.length}): ${nomearThreads(r.threadsBloqueadas)}`] : []),
233
241
  ...(r.tecnicos ? ['', ` ${linhaDosTecnicos(r)}`] : []),
234
242
  ...(r.consertos ? ['', ` ${linhaDosConsertos(r.consertos)}`] : []),
243
+ ...linhaDoHitlParado(r).flatMap(l => ['', ` ${l}`]),
235
244
  ...(r.paradosNoCondutor?.length ? ['', ...linhasDoCondutor(r, ' ', ' ')] : []),
236
245
  ...(r.acumuladas ? ['', ` ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
237
246
  ...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '', ' ').map(l => l.startsWith(' ') ? l : ` ${l}`)] : []),
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JANELA_DA_MEDIANA_MS = exports.META_DA_MEDIANA_DE_RESPOSTA_MS = void 0;
4
+ exports.esperasDeHitl = esperasDeHitl;
5
+ exports.resumirTempoParado = resumirTempoParado;
6
+ exports.hitlDeConducaoAgora = hitlDeConducaoAgora;
7
+ exports.lerHitlDeConducaoAgora = lerHitlDeConducaoAgora;
8
+ exports.textoDaMediana = textoDaMediana;
9
+ exports.linhaDaPerguntaParada = linhaDaPerguntaParada;
10
+ exports.linhaDoHitlAcimaDaMeta = linhaDoHitlAcimaDaMeta;
11
+ /**
12
+ * RM-057 (fatia 2): o tempo parado por HITL de conducao, medido do ledger.
13
+ *
14
+ * A metrica principal da RM-057 e o tempo em que a fabrica fica parada esperando o dono numa
15
+ * pergunta de conducao. A linha de base e a noite de 01 para 02/10/2026: mais de 10 h parada num
16
+ * "confirmo" em texto livre. A meta e mediana de resposta abaixo de 5 min e nenhum pedido em texto
17
+ * fora da excecao tecnica.
18
+ *
19
+ * O que conta como HITL de conducao: o pedido `ork.hitl/v2` de classe `pergunta` que o ork abriu
20
+ * (`hitl_requested`). A decisao informada (`decidido`) nao para nada e fica fora; o v1 e a pergunta
21
+ * nativa de uma sessao do host, com as opcoes do host, e tambem fica fora.
22
+ *
23
+ * Quando a espera acaba, na ordem em que o ledger prova:
24
+ *
25
+ * respondido o primeiro `human_gate` ou `session_answered` com o `pedidoId`;
26
+ * seguiu-recomendada o prazo venceu e o pedido declara `seguir-recomendada` (ele se resolve so);
27
+ * substituido um pedido novo para o mesmo alvo (o velho deixou de ser a pergunta);
28
+ * encerrado a thread fechou (`master_done` ou `thread_closed_admin`) sem resposta;
29
+ * aberto nada disso ainda: a espera corre ate o fim do periodo.
30
+ *
31
+ * Limite dito: o "confirmo" que um condutor pede na conversa, fora do ork, nao chega ao ledger e
32
+ * nao entra aqui. Por isso o canario `fx-pedido-colado` e a regra nos adaptadores existem.
33
+ *
34
+ * Funcoes puras sobre eventos: nao leem disco nem relogio. Ausencia de pedido e zero pedidos, e a
35
+ * mediana sem resposta e `null`, nunca zero inventado. A unica leitura de disco e
36
+ * `lerHitlDeConducaoAgora`, que so junta os ledgers e entrega a conta as funcoes puras.
37
+ *
38
+ * Fatia 3: o mesmo tempo parado chega ao `ork pulse` e ao `ork roadmap status`, com as perguntas
39
+ * abertas agora (ha quanto tempo cada uma espera) e a mediana dos ultimos 7 dias.
40
+ */
41
+ const hitl_contract_1 = require("./hitl-contract");
42
+ const horario_1 = require("./horario");
43
+ const ledger_1 = require("./ledger");
44
+ const thread_1 = require("./thread");
45
+ /** A meta da RM-057: mediana de resposta abaixo de 5 minutos. */
46
+ exports.META_DA_MEDIANA_DE_RESPOSTA_MS = 5 * 60_000;
47
+ const RESPOSTAS = ['human_gate', 'session_answered'];
48
+ const FECHAMENTOS_DA_THREAD = ['master_done', 'thread_closed_admin'];
49
+ function instante(valor) {
50
+ const t = typeof valor === 'string' ? Date.parse(valor) : NaN;
51
+ return Number.isFinite(t) ? t : null;
52
+ }
53
+ function formaDaResposta(p) {
54
+ const pedeTexto = p.tipoDeResposta === 'aberta' || p.respostaAceita?.tipo === 'texto';
55
+ if (!pedeTexto)
56
+ return 'selecao';
57
+ return p.dependenciaTecnica ? 'dependencia' : 'fora-da-excecao';
58
+ }
59
+ function chaveDoAlvo(p) {
60
+ try {
61
+ const alvo = (0, hitl_contract_1.alvoDoPedido)(p);
62
+ if (alvo)
63
+ return JSON.stringify(alvo);
64
+ }
65
+ catch { /* sem alvo legivel */ }
66
+ return `pedido:${String(p.id)}`;
67
+ }
68
+ /** As esperas de uma thread, em ordem de abertura. Le so o ledger dela. */
69
+ function esperasDeHitl(thread, eventos) {
70
+ const esperas = [];
71
+ const vistos = new Set();
72
+ for (const e of eventos) {
73
+ if (e.tipo !== 'hitl_requested')
74
+ continue;
75
+ const p = e.pedido;
76
+ if (!p || typeof p !== 'object' || !(0, hitl_contract_1.ehV2)(p) || p.classe !== 'pergunta' || vistos.has(String(p.id)))
77
+ continue;
78
+ const inicioMs = instante(p.criadoEm) ?? instante(e.ts);
79
+ if (inicioMs === null)
80
+ continue;
81
+ vistos.add(String(p.id));
82
+ esperas.push({ thread, pedidoId: String(p.id), fase: String(p.fase), inicioMs, fimMs: null, fim: 'aberto',
83
+ forma: formaDaResposta(p), alvo: chaveDoAlvo(p), prazoMs: instante(p.prazo),
84
+ seguir: p.acaoPadraoAoExpirar === 'seguir-recomendada' });
85
+ }
86
+ const fechar = (x, fimMs, fim) => {
87
+ if (x.fimMs === null || fimMs < x.fimMs) {
88
+ x.fimMs = Math.max(fimMs, x.inicioMs);
89
+ x.fim = fim;
90
+ }
91
+ };
92
+ for (const x of esperas) {
93
+ const resposta = eventos.find(e => RESPOSTAS.includes(e.tipo) && String(e.pedidoId) === x.pedidoId && instante(e.ts) !== null);
94
+ if (resposta)
95
+ fechar(x, instante(resposta.ts), 'respondido');
96
+ if (x.seguir && x.prazoMs !== null)
97
+ fechar(x, x.prazoMs, 'seguiu-recomendada');
98
+ const novo = esperas.find(y => y !== x && y.alvo === x.alvo && y.inicioMs > x.inicioMs);
99
+ if (novo)
100
+ fechar(x, novo.inicioMs, 'substituido');
101
+ const fechou = eventos.find(e => FECHAMENTOS_DA_THREAD.includes(e.tipo) && (instante(e.ts) ?? -Infinity) >= x.inicioMs);
102
+ if (fechou)
103
+ fechar(x, instante(fechou.ts), 'encerrado');
104
+ }
105
+ return esperas.map(({ alvo: _a, prazoMs: _p, seguir: _s, ...x }) => x);
106
+ }
107
+ function mediana(valores) {
108
+ if (valores.length === 0)
109
+ return null;
110
+ const v = [...valores].sort((a, b) => a - b), m = Math.floor(v.length / 2);
111
+ return v.length % 2 ? v[m] : (v[m - 1] + v[m]) / 2;
112
+ }
113
+ /** Resume as esperas de todas as threads no periodo `[desdeMs, ateMs)`. */
114
+ function resumirTempoParado(esperas, desdeMs, ateMs) {
115
+ let pedidos = 0, respondidos = 0, semResposta = 0, abertos = 0, paradoMs = 0, comDependencia = 0, foraDaExcecao = 0;
116
+ const respostas = [];
117
+ for (const x of esperas) {
118
+ const fim = x.fimMs ?? ateMs;
119
+ const a = Math.max(x.inicioMs, desdeMs), b = Math.min(fim, ateMs);
120
+ if (b > a)
121
+ paradoMs += b - a;
122
+ if (x.inicioMs >= desdeMs && x.inicioMs < ateMs) {
123
+ pedidos++;
124
+ if (x.forma === 'dependencia')
125
+ comDependencia++;
126
+ if (x.forma === 'fora-da-excecao')
127
+ foraDaExcecao++;
128
+ }
129
+ if (x.fimMs === null || x.fimMs >= ateMs) {
130
+ if (x.inicioMs < ateMs)
131
+ abertos++;
132
+ continue;
133
+ }
134
+ if (x.fimMs < desdeMs)
135
+ continue;
136
+ if (x.fim === 'respondido') {
137
+ respondidos++;
138
+ respostas.push(x.fimMs - x.inicioMs);
139
+ }
140
+ else
141
+ semResposta++;
142
+ }
143
+ const med = mediana(respostas);
144
+ return {
145
+ pedidos, respondidos, semResposta, abertos, paradoMs,
146
+ medianaRespostaMs: med, maiorRespostaMs: respostas.length ? Math.max(...respostas) : null,
147
+ metaMedianaMs: exports.META_DA_MEDIANA_DE_RESPOSTA_MS, dentroDaMeta: med === null ? null : med < exports.META_DA_MEDIANA_DE_RESPOSTA_MS,
148
+ emTexto: { comDependenciaTecnica: comDependencia, foraDaExcecao },
149
+ };
150
+ }
151
+ /** RM-057 (fatia 3): a janela da mediana que o pulse e o status mostram. */
152
+ exports.JANELA_DA_MEDIANA_MS = 7 * 24 * 60 * 60_000;
153
+ /** O retrato num instante. Pura: `quando` e o relogio de quem chama. */
154
+ function hitlDeConducaoAgora(esperas, quando) {
155
+ const agoraMs = Date.parse(quando);
156
+ const abertas = esperas
157
+ .filter(x => x.inicioMs <= agoraMs && (x.fimMs === null || x.fimMs > agoraMs))
158
+ .sort((a, b) => a.inicioMs - b.inicioMs || a.thread.localeCompare(b.thread))
159
+ .map(x => ({ thread: x.thread, pedidoId: x.pedidoId, fase: x.fase, desdeEm: new Date(x.inicioMs).toISOString(),
160
+ paradaHaMin: Math.floor((agoraMs - x.inicioMs) / 60_000) }));
161
+ const seteDias = resumirTempoParado(esperas, agoraMs - exports.JANELA_DA_MEDIANA_MS, agoraMs);
162
+ const abertaAcima = abertas.some(a => (agoraMs - Date.parse(a.desdeEm)) > exports.META_DA_MEDIANA_DE_RESPOSTA_MS);
163
+ return { abertas, seteDias, acimaDaMeta: abertaAcima || seteDias.dentroDaMeta === false };
164
+ }
165
+ /** Junta os ledgers de todas as threads do projeto e devolve o retrato. Ledger ilegivel fica de fora. */
166
+ function lerHitlDeConducaoAgora(raiz, quando) {
167
+ const esperas = [];
168
+ for (const id of (0, thread_1.listarIds)(raiz)) {
169
+ try {
170
+ esperas.push(...esperasDeHitl(id, (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id))));
171
+ }
172
+ catch { /* ledger ilegivel nao para o pulse */ }
173
+ }
174
+ return hitlDeConducaoAgora(esperas, quando);
175
+ }
176
+ const emMinutos = (ms) => `${(ms / 60_000).toFixed(1)} min`;
177
+ /** "mediana de 7 dias 3.0 min (dentro da meta de 5 min)", ou sem resposta medida. */
178
+ function textoDaMediana(h) {
179
+ const s = h.seteDias;
180
+ 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))`;
184
+ }
185
+ /** "<thread> (<fase>) parada ha 12min, desde 01:02": a hora no fuso do dono (RM-035). */
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)}`;
188
+ }
189
+ /**
190
+ * A linha unica do resumo do pulse, so quando passou da meta: quantas abertas, a mais antiga e a
191
+ * mediana. Abaixo da meta, nada: o resumo nao ganha uma linha que nao pede atencao.
192
+ */
193
+ function linhaDoHitlAcimaDaMeta(h, opcoes) {
194
+ if (!h || !h.acimaDaMeta)
195
+ return null;
196
+ const velha = h.abertas[0];
197
+ const abertas = h.abertas.length
198
+ ? `${h.abertas.length} pergunta(s) de condução aberta(s), a mais antiga ${linhaDaPerguntaParada(velha, opcoes)}; `
199
+ : 'nenhuma pergunta de condução aberta; ';
200
+ return `HITL de condução acima da meta de 5 min: ${abertas}${textoDaMediana(h)}.`;
201
+ }
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * "Inequivoca" nao e julgamento de ninguem. E esta regra, fechada e pura:
12
12
  *
13
- * 1. o texto tem a forma de uma resposta: letra a-d, digito 1-4, ou uma palavra do VOCABULARIO
13
+ * 1. o texto tem a forma de uma resposta: letra a-e, digito 1-5, ou uma palavra do VOCABULARIO
14
14
  * (que e uma lista, e nao um classificador), sozinha ou numerada ("1. B, 2. aprovo");
15
15
  * 2. a palavra casa com UMA alternativa, pela acao dela ("aprovo" casa `aprovar`; "nao" num gate
16
16
  * casa `recusar` e `esperar`, entao e ambigua e volta como pergunta);
@@ -62,7 +62,7 @@ const PALAVRAS_DA_FORMA = 'pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|apr
62
62
  'confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|' +
63
63
  'volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?';
64
64
  /** Uma resposta: letra, ou palavra do vocabulario. */
65
- const RESPOSTA_DA_FORMA = `(?:[a-d]|${PALAVRAS_DA_FORMA})`;
65
+ const RESPOSTA_DA_FORMA = `(?:[a-e]|${PALAVRAS_DA_FORMA})`;
66
66
  /**
67
67
  * Um item numerado: "1a", "1 a", "1. B", "1) aprovo", "1 - nao", "1: detalhes".
68
68
  *
@@ -78,7 +78,7 @@ exports.FORMAS_DO_TEXTO_LIVRE = Object.freeze({
78
78
  /** Resposta numerada, uma ou varias, separadas por virgula, ponto e virgula, espaco ou linha. */
79
79
  lista: `^[ \\t]*${ITEM_DA_FORMA}(?:[ \\t\\r\\n,;]*${ITEM_DA_FORMA})*[ \\t]*(?:[.!][ \\t]*)?$`,
80
80
  /** Resposta solta: letra, digito ou palavra, sozinha na mensagem. */
81
- livre: `^[ \\t]*(?:[1-4]|${RESPOSTA_DA_FORMA})[ \\t]*(?:[.!]+[ \\t]*)?$`,
81
+ livre: `^[ \\t]*(?:[1-5]|${RESPOSTA_DA_FORMA})[ \\t]*(?:[.!]+[ \\t]*)?$`,
82
82
  });
83
83
  /** Minusculas, sem acento, espaco unico, sem pontuacao final. */
84
84
  function normalizarLivre(texto) {
@@ -87,9 +87,9 @@ function normalizarLivre(texto) {
87
87
  /** O que um pedaco de resposta quer dizer, ou `null` quando ele nao e resposta. */
88
88
  function lerTrecho(bruto) {
89
89
  const t = normalizarLivre(bruto);
90
- if (/^[a-d]$/.test(t))
90
+ if (/^[a-e]$/.test(t))
91
91
  return { tipo: 'letra', letra: t };
92
- if (/^[1-4]$/.test(t))
92
+ if (/^[1-5]$/.test(t))
93
93
  return { tipo: 'digito', numero: Number(t) };
94
94
  for (const [intencao, palavras] of Object.entries(exports.VOCABULARIO_LIVRE)) {
95
95
  if (palavras.includes(t))
package/dist/horario.js CHANGED
@@ -22,6 +22,7 @@ exports.definirFusoDoDono = definirFusoDoDono;
22
22
  exports.fusoDoDono = fusoDoDono;
23
23
  exports.partesLocais = partesLocais;
24
24
  exports.dataLocal = dataLocal;
25
+ exports.instanteNoFuso = instanteNoFuso;
25
26
  exports.duracaoCurta = duracaoCurta;
26
27
  exports.duracaoRelativa = duracaoRelativa;
27
28
  exports.rotuloDoFuso = rotuloDoFuso;
@@ -153,6 +154,32 @@ function dataLocal(quando, fuso = fusoDoDono().fuso) {
153
154
  const p = partesLocais(quando, fuso);
154
155
  return `${p.ano}-${p.mes}-${p.dia}`;
155
156
  }
157
+ /** Quanto o relógio do fuso está à frente do UTC no instante, em milissegundos. */
158
+ function desvioDoFuso(ms, fuso) {
159
+ const p = partesLocais(ms, fuso);
160
+ return Date.UTC(Number(p.ano), Number(p.mes) - 1, Number(p.dia), Number(p.hora), Number(p.minuto), Number(p.segundo)) -
161
+ Math.floor(ms / 1000) * 1000;
162
+ }
163
+ /**
164
+ * RM-037 (fatia 5): o instante de um relógio num fuso IANA (mês de 1 a 12). O runtime diz a hora de
165
+ * volta da cota no fuso dele (`resets 4:40am (<fuso>)`), e lê-la no relógio desta máquina errava o
166
+ * prazo em quem roda noutro fuso. Fuso que o Intl recusa e data que não existe voltam NaN, sem chute.
167
+ * Na volta do horário de verão vale o primeiro dos dois instantes; o relógio que o salto pula cai
168
+ * depois do salto.
169
+ */
170
+ function instanteNoFuso(ano, mes, dia, hora, minuto, fuso) {
171
+ const nome = normalizarFuso(fuso);
172
+ const base = Date.UTC(ano, mes - 1, dia, hora, minuto, 0, 0);
173
+ const data = new Date(Date.UTC(ano, mes - 1, dia));
174
+ if (!nome || !Number.isFinite(base) || data.getUTCFullYear() !== ano || data.getUTCMonth() !== mes - 1 ||
175
+ data.getUTCDate() !== dia)
176
+ return NaN;
177
+ // Os desvios de um dia antes e de um dia depois cobrem os dois lados de qualquer mudança de horário.
178
+ const candidatos = [...new Set([base - 86400000, base, base + 86400000].map((ms) => base - desvioDoFuso(ms, nome)))]
179
+ .sort((a, b) => a - b);
180
+ const exatos = candidatos.filter((ms) => ms + desvioDoFuso(ms, nome) === base);
181
+ return exatos[0] ?? base - desvioDoFuso(base - 86400000, nome);
182
+ }
156
183
  // ---------------------------------------------------------------------------
157
184
  // Texto para pessoa
158
185
  // ---------------------------------------------------------------------------
package/dist/hosts.js CHANGED
@@ -57,6 +57,7 @@ exports.ORDEM_DOS_HOSTS = exports.HOSTS = exports.MAESTRO_HOST_SURFACES = export
57
57
  exports.parseHost = parseHost;
58
58
  exports.lerRecibo = lerRecibo;
59
59
  exports.classificarDivergencia = classificarDivergencia;
60
+ exports.avisoDoOrkNoPath = avisoDoOrkNoPath;
60
61
  exports.renderizarArquivo = renderizarArquivo;
61
62
  exports.instalarAdaptador = instalarAdaptador;
62
63
  exports.desinstalarExperiencia = desinstalarExperiencia;
@@ -64,6 +65,7 @@ exports.tabelaDeHosts = tabelaDeHosts;
64
65
  exports.textoDosPitfalls = textoDosPitfalls;
65
66
  exports.textoDaInstalacao = textoDaInstalacao;
66
67
  const node_crypto_1 = require("node:crypto");
68
+ const node_child_process_1 = require("node:child_process");
67
69
  const fs = __importStar(require("node:fs"));
68
70
  exports.BRAIN_HOST_SURFACES = {
69
71
  hermes: 'cli', openclaw: 'cli', 'claude-code': 'mcp', codex: 'mcp',
@@ -246,6 +248,31 @@ function listarArquivos(dir, prefixo = '') {
246
248
  }
247
249
  return achados;
248
250
  }
251
+ function mesmoArquivo(a, b) {
252
+ try {
253
+ return fs.realpathSync(a) === fs.realpathSync(b);
254
+ }
255
+ catch {
256
+ return path.resolve(a) === path.resolve(b);
257
+ }
258
+ }
259
+ /**
260
+ * Ensaio de 03/10: com outro `ork` primeiro no PATH, a extensão segue apontando para o binário que roda o
261
+ * install, e o aviso diz as duas versões para ninguém descobrir a diferença pela extensão chamando o velho.
262
+ */
263
+ function avisoDoOrkNoPath(emExecucao, versao, env = process.env) {
264
+ const doPath = (0, util_1.noPath)('ork', env);
265
+ if (!doPath || mesmoArquivo(doPath, emExecucao))
266
+ return undefined;
267
+ let outra = 'versão desconhecida';
268
+ try {
269
+ const saida = (0, node_child_process_1.execFileSync)(doPath, ['--version'], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], env });
270
+ outra = saida.trim().split('\n')[0]?.trim() || outra;
271
+ }
272
+ catch { /* o aviso sai mesmo sem a versão */ }
273
+ return `o ork do PATH (${doPath}, ${outra}) não é o que roda este install (${emExecucao}, ${versao}). ` +
274
+ `A extensão chama ${emExecucao}; para outro binário, passe --ork <caminho>.`;
275
+ }
249
276
  /** Substitui os tres placeholders. Nao ha template engine aqui de proposito. */
250
277
  function renderizarArquivo(conteudo, valores) {
251
278
  return conteudo
@@ -267,8 +294,10 @@ function instalarAdaptador(host, opcoes = {}) {
267
294
  ? path.resolve(projeto, opcoes.dir)
268
295
  : path.join(projeto, def.destinoPadrao);
269
296
  const destino = def.subdir ? path.join(base, def.subdir) : base;
270
- const orkBin = opcoes.orkBin ?? (0, util_1.noPath)('ork') ?? (0, catalogo_1.cliDoCatalogo)(catalogo);
297
+ const orkBin = opcoes.orkBin ?? opcoes.orkEmExecucao ?? (0, util_1.noPath)('ork') ?? (0, catalogo_1.cliDoCatalogo)(catalogo);
271
298
  const versao = opcoes.versao ?? versao_1.VERSAO_DO_ORK;
299
+ const avisoOrk = opcoes.orkBin === undefined && opcoes.orkEmExecucao
300
+ ? avisoDoOrkNoPath(opcoes.orkEmExecucao, versao) : undefined;
272
301
  const dirDoHost = path.join(catalogo, 'adapters', host);
273
302
  if (!fs.existsSync(dirDoHost)) {
274
303
  throw new Error(`adaptador do host ${host} nao existe em ${dirDoHost}`);
@@ -424,6 +453,7 @@ function instalarAdaptador(host, opcoes = {}) {
424
453
  ...(reciboVizinho ? { reciboVizinho } : {}),
425
454
  ok: !barrado,
426
455
  orkBin,
456
+ ...(avisoOrk ? { avisoOrk } : {}),
427
457
  pitfalls: def.pitfalls,
428
458
  ...(preferencias || avisoExperiencia ? { experiencia: { ativa: host !== 'openclaw' && !!preferencias?.experience && !avisoExperiencia,
429
459
  arquivos: planoExperiencia?.mudancas.map(m => path.relative(planoExperiencia.projeto, m.arquivo)) ?? [], skill: preferencias?.skill ?? '',
@@ -523,28 +553,35 @@ function textoDaInstalacao(r) {
523
553
  : r.ok
524
554
  ? `Arquivos do adaptador ${r.host} preparados.`
525
555
  : `Instalacao do adaptador ${r.host} BARRADA: ha arquivo diferente no destino.`);
556
+ // Ensaio de 03/10 (R6): a ativacao sai no comeco e de novo no fim, depois da lista de arquivos e dos
557
+ // pitfalls, para que o fim da saida diga o proximo passo.
558
+ const ativacao = [];
526
559
  if (r.ok && !r.dryRun && r.host === 'claude-code') {
527
560
  const destino = "'" + r.destino.replace(/'/g, "'\"'\"'") + "'";
528
561
  linhas.push('Uso no Claude Code ainda nao verificado por esta copia.');
529
562
  linhas.push('No diretorio deste projeto, valide e ative o plugin:');
530
- linhas.push(` claude plugin validate ${destino}`);
531
- linhas.push(` claude plugin marketplace add ${destino} --scope project`);
532
- linhas.push(' claude plugin install orkastery@orkastery --scope project');
533
- linhas.push(' claude plugin list --json');
534
- linhas.push('Depois, na sessao deste projeto: /orkastery:ork');
563
+ ativacao.push(` claude plugin validate ${destino}`);
564
+ ativacao.push(` claude plugin marketplace add ${destino} --scope project`);
565
+ ativacao.push(' claude plugin install orkastery@orkastery --scope project');
566
+ ativacao.push(' claude plugin list --json');
567
+ ativacao.push('Depois, na sessao deste projeto: /orkastery:ork');
535
568
  }
536
569
  else if (r.ok && !r.dryRun && r.host === 'codex') {
537
- linhas.push('Abra o Codex neste projeto e use $ork.');
538
- linhas.push('Descoberta e uso nativo ainda nao verificados por esta copia.');
570
+ ativacao.push('Abra o Codex neste projeto e use $ork.');
571
+ ativacao.push('Descoberta e uso nativo ainda nao verificados por esta copia.');
539
572
  }
540
573
  if (r.ok && !r.dryRun && (r.host === 'codex' || r.host === 'claude-code')) {
541
574
  const projeto = "'" + r.projeto.replace(/'/g, "'\"'\"'") + "'";
542
- linhas.push(`Prepare tambem as ferramentas do projeto: ork mcp install --project ${projeto} --host ${r.host}`);
543
- linhas.push('A configuracao MCP exige descoberta e consentimento no cliente; a copia nao concede aprovacoes.');
575
+ ativacao.push(`Prepare tambem as ferramentas do projeto: ork mcp install --project ${projeto} --host ${r.host}`);
544
576
  }
577
+ linhas.push(...ativacao);
578
+ if (ativacao.length)
579
+ linhas.push('A configuracao MCP exige descoberta e consentimento no cliente; a copia nao concede aprovacoes.');
545
580
  linhas.push(` catalogo: ${r.catalogo}`);
546
581
  linhas.push(` destino: ${r.destino}`);
547
582
  linhas.push(` ork: ${r.orkBin}`);
583
+ if (r.avisoOrk)
584
+ linhas.push(` Aviso: ${r.avisoOrk}`);
548
585
  linhas.push('');
549
586
  const novos = r.arquivos.filter((a) => a.estado === 'novo').length;
550
587
  const iguais = r.arquivos.filter((a) => a.estado === 'igual').length;
@@ -592,5 +629,10 @@ function textoDaInstalacao(r) {
592
629
  }
593
630
  linhas.push('');
594
631
  linhas.push(textoDosPitfalls(r.host));
632
+ if (ativacao.length) {
633
+ linhas.push('');
634
+ linhas.push(r.host === 'claude-code' ? 'Proximo passo, no diretorio deste projeto (o mesmo do comeco):' : 'Proximo passo (o mesmo do comeco):');
635
+ linhas.push(...ativacao);
636
+ }
595
637
  return linhas.join('\n');
596
638
  }