@orkastery/cli 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/agents/ork-check.md +1 -1
  4. package/adapters/claude-code/agents/ork-go.md +2 -2
  5. package/adapters/claude-code/agents/ork-goal.md +1 -1
  6. package/adapters/claude-code/agents/ork-master.md +1 -1
  7. package/adapters/claude-code/agents/ork-plan.md +1 -1
  8. package/adapters/claude-code/agents/ork-ship.md +1 -1
  9. package/adapters/claude-code/commands/master.md +11 -8
  10. package/adapters/claude-code/commands/ork.md +8 -0
  11. package/adapters/claude-code/commands/ship.md +2 -2
  12. package/adapters/codex/skills/ork/SKILL.md +12 -1
  13. package/adapters/hermes/README.md +3 -0
  14. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  15. package/adapters/hermes/bin/ork-brain.sh +3 -0
  16. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  17. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  18. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  19. package/adapters/hermes/hermes.plugin.json +2 -0
  20. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  21. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  22. package/adapters/openclaw/README.md +34 -2
  23. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  24. package/adapters/openclaw/dist/index.js +92 -20
  25. package/adapters/openclaw/openclaw.plugin.json +10 -1
  26. package/adapters/openclaw/package.json +2 -2
  27. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  28. package/adapters/openclaw/src/index.ts +108 -20
  29. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  30. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  31. package/assets/docs/produto/_modelo-feature.md +2 -0
  32. package/assets/orkmind_bridge.py +214 -1
  33. package/dist/adapters/claude-bg.js +6 -3
  34. package/dist/board.js +74 -0
  35. package/dist/branch-de-estado.js +5 -4
  36. package/dist/busca-semantica.js +161 -0
  37. package/dist/ci.js +141 -10
  38. package/dist/company-brain-cli.js +27 -2
  39. package/dist/company-brain-context.js +132 -0
  40. package/dist/company-brain-dossie.js +376 -0
  41. package/dist/company-brain-mcp.js +4 -1
  42. package/dist/conducao.js +71 -18
  43. package/dist/decisao-autonoma.js +42 -0
  44. package/dist/docs.js +64 -22
  45. package/dist/doctor.js +33 -0
  46. package/dist/entrega-pr.js +109 -5
  47. package/dist/experiencia-instalacao.js +256 -0
  48. package/dist/experiencia.js +72 -0
  49. package/dist/fabrica-estado.js +43 -10
  50. package/dist/fechamento.js +105 -0
  51. package/dist/forja.js +435 -0
  52. package/dist/gates.js +9 -1
  53. package/dist/hitl-classificacao.js +11 -1
  54. package/dist/hitl-contract.js +50 -7
  55. package/dist/hitl-curto.js +158 -0
  56. package/dist/hitl-gates.js +67 -1
  57. package/dist/hitl-lote.js +30 -15
  58. package/dist/hitl-presentation.js +10 -7
  59. package/dist/hitl-resumo.js +33 -12
  60. package/dist/hitl-texto-livre.js +150 -0
  61. package/dist/hosts.js +86 -1
  62. package/dist/index.js +602 -38
  63. package/dist/indice-vetorial.js +317 -0
  64. package/dist/init.js +13 -0
  65. package/dist/integracoes-locais.js +1 -1
  66. package/dist/intelligence-benchmark-contract.js +570 -0
  67. package/dist/intelligence-graph-cli.js +430 -0
  68. package/dist/intelligence-graph-contract.js +577 -0
  69. package/dist/intelligence-graph-extract-md.js +469 -0
  70. package/dist/intelligence-graph-extract-ts.js +810 -0
  71. package/dist/intelligence-graph-extract.js +369 -0
  72. package/dist/intelligence-graph-index.js +512 -0
  73. package/dist/intelligence-graph-parsers.js +327 -0
  74. package/dist/intelligence-graph-query.js +344 -0
  75. package/dist/intelligence-graph-repo.js +187 -0
  76. package/dist/leases.js +84 -0
  77. package/dist/ledger.js +14 -0
  78. package/dist/maestro-cli.js +10 -1
  79. package/dist/maestro-contract.js +5 -1
  80. package/dist/maestro-discovery.js +16 -2
  81. package/dist/maestro-snapshot.js +6 -2
  82. package/dist/manifest.js +142 -5
  83. package/dist/master-batch.js +3 -2
  84. package/dist/master-digest.js +2 -2
  85. package/dist/master-nota.js +203 -0
  86. package/dist/master.js +4 -0
  87. package/dist/mcp-experiencia.js +164 -0
  88. package/dist/mcp-git.js +4 -0
  89. package/dist/mcp-install.js +3 -1
  90. package/dist/mcp-maestro.js +3 -2
  91. package/dist/mcp-server.js +84 -5
  92. package/dist/memoria.js +147 -1
  93. package/dist/network-roadmap.js +907 -0
  94. package/dist/ocupacao.js +38 -2
  95. package/dist/onboarding.js +113 -7
  96. package/dist/orkmind.js +165 -9
  97. package/dist/phase.js +166 -13
  98. package/dist/policies.js +2 -0
  99. package/dist/projeto-alvo.js +551 -0
  100. package/dist/prompts.js +1 -1
  101. package/dist/pulse-consentimento.js +4 -2
  102. package/dist/pulse-delivery.js +5 -0
  103. package/dist/pulse-resposta.js +674 -49
  104. package/dist/pulse.js +21 -0
  105. package/dist/retry.js +51 -8
  106. package/dist/roadmap-reservas.js +262 -20
  107. package/dist/roadmap-status.js +213 -0
  108. package/dist/thread-close.js +3 -0
  109. package/dist/verify.js +4 -3
  110. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  111. package/eval/casos/orchestration-experience.json +48 -0
  112. package/package.json +1 -1
  113. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  114. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  115. package/schemas/maestro-snapshot.schema.json +23 -0
  116. package/skills/README.md +7 -5
  117. package/skills/core/onboarding/SKILL.md +7 -0
  118. package/skills/core/orchestration-experience/SKILL.md +113 -0
  119. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  120. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  121. package/skills/phases/go-implementation/SKILL.md +2 -0
@@ -76,6 +76,9 @@ async function rodarOrk(args, signal, entrada) {
76
76
  env.ORK_RECEIPT_VERIFIERS = verifiers;
77
77
  // I-36 (D6): o adaptador declara o proprio canal de conducao. Descreve a porta, nao concede autoridade.
78
78
  env.ORK_CANAL = 'openclaw';
79
+ // RM-052 (D3): o cwd do gateway nao e projeto de ninguem. Sem `projeto` pedido, o nucleo usa o unico
80
+ // projeto conhecido da maquina ou devolve a escolha; nunca o manifesto que estiver no cwd do gateway.
81
+ env.ORK_PROJETO_EXPLICITO = '1';
79
82
  return new Promise((resolve) => {
80
83
  const filho = execFile(bin, args, { maxBuffer: MAX_BUFFER, signal, env }, (erro, stdout, stderr) => {
81
84
  const partes = [stdout, stderr].map((t) => (t ?? '').trim()).filter((t) => t !== '');
@@ -156,10 +159,36 @@ function argvDaResposta(p, alvo) {
156
159
  return [alvo, 'answer', thread, pedido, '--resposta-stdin', '--por', `telegram:${usuario}`,
157
160
  '--mensagem', `telegram:${chat}:${referencia}`, '--origem', 'telegram'];
158
161
  }
162
+ /**
163
+ * RM-052 (D4): o projeto pedido chega como NOME de projeto registrado (`ork projetos`), nunca como
164
+ * caminho. O modelo escolhe entre os projetos da maquina; nao aponta o nucleo para um diretorio.
165
+ */
166
+ const PADRAO_DO_PROJETO = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
167
+ const PARAMETRO_PROJETO = {
168
+ type: 'string',
169
+ pattern: PADRAO_DO_PROJETO.source,
170
+ description: 'Nome do projeto que o dono pediu (ex.: orkastery), como em `ork projetos`. Sem ele e com mais de um projeto na máquina, a resposta é a escolha; nunca use o diretório do gateway.',
171
+ };
172
+ const PROJETO_DA_RM052 = {
173
+ padrao: PADRAO_DO_PROJETO,
174
+ descricao: PARAMETRO_PROJETO.description,
175
+ recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery), nunca um caminho',
176
+ };
177
+ /**
178
+ * RM-054 (fatia 2, D-G4): no roadmap da rede, `projeto` tambem aceita a forja (`github:dono/repo`,
179
+ * `gitlab:grupo/repo`): numa maquina sem clone, e o unico caminho ate o projeto enquanto a rede por
180
+ * pessoa (RM-053) nao chega. Caminho e URL continuam fora (a URL pode levar credencial), e o nucleo
181
+ * confere de novo, porque o adaptador declara ORK_PROJETO_EXPLICITO=1.
182
+ */
183
+ const PROJETO_DA_REDE = {
184
+ padrao: /^(?:[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}|(?:github|gitlab):[A-Za-z0-9][A-Za-z0-9._-]{0,99}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}){1,8})$/,
185
+ descricao: 'Projeto que o dono pediu: o nome, como em `ork projetos` (ex.: orkastery), ou a forja (github:dono/repo, gitlab:grupo/repo) quando não há clone nesta máquina. Sem ele, vêm todos os projetos conhecidos. Nunca caminho nem URL.',
186
+ recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery) ou github:dono/repo, nunca caminho nem URL',
187
+ };
159
188
  const FERRAMENTAS = [
160
189
  {
161
190
  name: 'ork_maestro',
162
- description: 'Ao receber a frase exata orkastery maestro, consulte o panorama somente leitura. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. Ação posterior exige operação autorizada e readback.',
191
+ description: 'Ao receber a frase exata orkastery maestro: sem projeto nomeado, chame ork_network_roadmap sem projeto (o panorama da rede, com fontes, frescor e lacunas) e apresente-o como vem; com projeto nomeado, consulte este panorama somente leitura com projeto. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. O panorama NÃO lê o roadmap, as reservas nem as outras máquinas (veja notConsulted): zero threads nunca é roadmap vazio; para o roadmap use ork_network_roadmap. Ação posterior exige operação autorizada e readback.',
163
192
  parameters: { type: 'object', additionalProperties: false, properties: {
164
193
  thread: { type: 'string' }, section: { type: 'string', enum: ['portfolio', 'demands', 'threads', 'sessions', 'blockers', 'leases', 'retries', 'hitl', 'ship', 'master', 'nextActions'] },
165
194
  offset: { type: 'integer', minimum: 0, maximum: 100000 },
@@ -281,6 +310,27 @@ const FERRAMENTAS = [
281
310
  parameters: schema({ thread: { type: 'string' }, id: { type: 'string' } }),
282
311
  argv: (p) => ['brain', 'get', texto(p, 'id'), '--thread', texto(p, 'thread')],
283
312
  },
313
+ {
314
+ name: 'ork_brain_context',
315
+ description: 'Pacote de contexto citável do Company Brain: entidades pedidas e seus pais, cada uma com a citação da fonte, o frescor contra o portfólio e as lacunas. Só leitura.',
316
+ parameters: schema({
317
+ thread: { type: 'string' },
318
+ ids: { type: 'string', description: 'ids do portfólio separados por vírgula (prod-, proj-, init-)' },
319
+ }),
320
+ argv: (p) => ['brain', 'context', '--thread', texto(p, 'thread'), '--ids', texto(p, 'ids')],
321
+ },
322
+ {
323
+ name: 'ork_brain_dossie',
324
+ description: 'Dossiê de decisão da thread: vínculo com objetivo e projeto, contexto citável, alternativas, decisão, quem decidiu e evidência, com os ids do Company Brain e as lacunas. Resposta do dono só com recibo conferido. Só leitura.',
325
+ parameters: {
326
+ type: 'object', additionalProperties: false, required: ['thread'],
327
+ properties: {
328
+ thread: { type: 'string' },
329
+ decisao: { type: 'string', description: 'id do pedido ou fact- da decisão; omita para a thread inteira' },
330
+ },
331
+ },
332
+ argv: (p) => ['brain', 'dossie', '--thread', texto(p, 'thread'), ...(p.decisao === undefined ? [] : ['--decisao', texto(p, 'decisao')])],
333
+ },
284
334
  {
285
335
  name: 'ork_claims_add',
286
336
  description: 'Registra uma alegacao verificavel com o comando que a comprova. Toda citacao de arquivo ou teste vira claim.',
@@ -358,30 +408,32 @@ const FERRAMENTAS = [
358
408
  },
359
409
  {
360
410
  name: 'ork_master',
361
- description: 'Fecha a thread: POSTMORTEM tipado, MASTER log no contrato congelado e o score HUMANO de 0 a 5 com justificativa. Nunca invente a nota.',
411
+ description: 'Pede ao dono a nota do MASTER (0 a 5, com o porque) de uma entrega: devolve a linha com o codigo curto para mostrar a ele. A nota volta pelo Telegram, com a prova do ingresso. Esta tool nunca grava nota e nao recebe nome de pessoa (RM-048).',
362
412
  parameters: schema({
363
413
  thread: { type: 'string' },
364
- score: { type: 'string', description: 'inteiro de 0 a 5, dado pelo humano' },
365
- justificativa: { type: 'string' },
366
- quem: { type: 'string' },
367
414
  }),
368
- argv: (p) => [
369
- 'master',
370
- texto(p, 'thread'),
371
- '--score',
372
- texto(p, 'score'),
373
- '--justificativa',
374
- texto(p, 'justificativa'),
375
- '--por',
376
- texto(p, 'quem'),
377
- ],
415
+ argv: (p) => ['master', 'pedir', texto(p, 'thread'), '--formato', 'telegram'],
378
416
  },
379
417
  {
380
418
  name: 'ork_board',
381
- description: 'Todas as threads em uma visao, e o escalonador dizendo quem avanca agora e quem espera.',
419
+ description: 'As threads DESTE projeto nesta maquina e o escalonador dizendo quem avanca agora e quem espera. NAO le o roadmap: nunca conclua sobre o roadmap a partir do board (zero threads nao e roadmap vazio); para o roadmap use ork_network_roadmap. O cabecalho diz o projeto consultado e o que nao foi lido.',
382
420
  parameters: schema({}),
383
421
  argv: () => ['board', 'plan'],
384
422
  },
423
+ {
424
+ name: 'ork_roadmap_status',
425
+ description: 'Status report do roadmap SO desta maquina (o checkout local), no formato aprovado pelo dono (grupos com icones, #HITL e o fecho): nao le as reservas nem as outras maquinas, e o cabecalho diz isso. Para o status do roadmap com as threads de todas as maquinas, as reservas, as fontes e as lacunas, use ork_network_roadmap. Somente leitura: transporte o texto como vem, sem reescrever. Passe projeto com o nome que o dono pediu (ex.: orkastery); sem ele e com mais de um projeto na maquina, a resposta e a escolha. Nunca deduza o roadmap de ork_board ou ork_maestro.',
426
+ parameters: schema({}),
427
+ argv: () => ['roadmap', 'status'],
428
+ },
429
+ {
430
+ name: 'ork_network_roadmap',
431
+ description: 'Roadmap da rede, somente leitura: para cada projeto, o status report do roadmap (RM-048) com as threads de TODAS as máquinas, as reservas, as threads por máquina com a idade da batida, a fonte e a hora de cada parte e as lacunas. É a fonte para qualquer pergunta sobre o roadmap ou o status report: transporte o texto como vem, sem reescrever nem resumir. Passe projeto com o nome que o dono pediu (ex.: orkastery) ou github:dono/repo; sem projeto, vêm todos os projetos conhecidos, e esse é o panorama da frase orkastery maestro sem projeto. Lacuna, "não lido" e "Não consultado" são fontes que ficaram sem leitura: nunca conclua "roadmap vazio" nem "nenhuma máquina publicou" a partir delas.',
432
+ parameters: schema({}),
433
+ projeto: PROJETO_DA_REDE,
434
+ // RM-054 (fatia 2): uma chamada de CLI; o `--projeto` vai no inicio e o nucleo o devolve ao `network`.
435
+ argv: () => ['network', 'roadmap'],
436
+ },
385
437
  {
386
438
  name: 'ork_master_batch',
387
439
  description: 'Todas as entregas em JSON, com o indice derivado do ledger, as ja pontuadas e as aceitas por omissao. Roda ork master --todas; o nome e da antiga fila de score (ork master --batch), aposentada na I-43.',
@@ -389,21 +441,41 @@ const FERRAMENTAS = [
389
441
  argv: () => ['master', '--todas', '--json'],
390
442
  },
391
443
  ];
444
+ /** RM-052: toda tool aceita `projeto` opcional, sem mudar o que ela ja exigia. */
445
+ function comProjeto(f) {
446
+ const propriedades = (f.parameters.properties ?? {});
447
+ const p = f.projeto;
448
+ const projeto = p ? { type: 'string', pattern: p.padrao.source, description: p.descricao } : PARAMETRO_PROJETO;
449
+ return { ...f.parameters, properties: { ...propriedades, projeto } };
450
+ }
451
+ /** `--projeto <nome>` vai no inicio do argv; o resto dos parametros segue para a tool como antes. */
452
+ function argvComProjeto(f, params) {
453
+ const { projeto, ...resto } = params;
454
+ if (projeto !== undefined && (typeof projeto !== 'string' || !(f.projeto ?? PROJETO_DA_RM052).padrao.test(projeto))) {
455
+ throw new Error('projeto.invalido');
456
+ }
457
+ return { argv: [...(projeto === undefined ? [] : ['--projeto', projeto]), ...f.argv(resto)], resto };
458
+ }
392
459
  const plugin = defineToolPlugin({
393
460
  id: 'orkastery',
394
461
  name: 'Orkastery',
395
- description: 'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.',
462
+ description: 'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap.',
396
463
  tools: (tool) => FERRAMENTAS.map((f) => tool({
397
464
  name: f.name,
398
465
  description: f.description,
399
- parameters: f.parameters,
466
+ parameters: comProjeto(f),
400
467
  execute: (params, _config, contexto) => {
468
+ let chamada;
401
469
  try {
402
- return rodarOrk(f.argv(params), contexto.signal, f.entrada?.(params));
470
+ chamada = argvComProjeto(f, params);
403
471
  }
404
- catch {
472
+ catch (e) {
473
+ if (e.message === 'projeto.invalido') {
474
+ return `[ork recusou] projeto.invalido: ${(f.projeto ?? PROJETO_DA_RM052).recusa}`;
475
+ }
405
476
  return '[ork recusou] resposta humana não confirmada; confira origem e correlação do pedido';
406
477
  }
478
+ return rodarOrk(chamada.argv, contexto.signal, f.entrada?.(chamada.resto));
407
479
  },
408
480
  })),
409
481
  });
@@ -2,7 +2,7 @@
2
2
  "id": "orkastery",
3
3
  "name": "Orkastery",
4
4
  "description": "Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.",
5
- "version": "0.4.3",
5
+ "version": "0.5.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "properties": {},
@@ -25,6 +25,8 @@
25
25
  "ork_brain_status",
26
26
  "ork_brain_query",
27
27
  "ork_brain_get",
28
+ "ork_brain_context",
29
+ "ork_brain_dossie",
28
30
  "ork_claims_add",
29
31
  "ork_verify",
30
32
  "ork_verify_baseline",
@@ -35,7 +37,14 @@
35
37
  "ork_ship",
36
38
  "ork_master",
37
39
  "ork_board",
40
+ "ork_roadmap_status",
41
+ "ork_network_roadmap",
38
42
  "ork_master_batch"
39
43
  ]
44
+ },
45
+ "toolMetadata": {
46
+ "ork_network_roadmap": {
47
+ "profiles": ["coding", "messaging"]
48
+ }
40
49
  }
41
50
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orkastery/openclaw-plugin",
3
- "version": "0.4.3",
4
- "description": "Plugin Orkastery para OpenClaw: 23 tools ork_* que traduzem intencao em chamada de CLI do nucleo `ork`. Zero regra de negocio no host.",
3
+ "version": "0.5.0",
4
+ "description": "Plugin Orkastery para OpenClaw: 27 tools ork_* que traduzem intencao em chamada de CLI do nucleo `ork`. Zero regra de negocio no host.",
5
5
  "type": "module",
6
6
  "private": true,
7
7
  "license": "MIT",
@@ -2,6 +2,8 @@
2
2
  /** D12: this host is the `openclaw` channel, and the channel is signed, not labelled. */
3
3
  import { createHash, createHmac } from 'node:crypto';
4
4
  import { execFile } from 'node:child_process';
5
+ import { readFileSync } from 'node:fs';
6
+ import { isAbsolute, join } from 'node:path';
5
7
 
6
8
  interface NativeEvent {
7
9
  content: string; channel: string; commandAuthorized?: boolean; accountId?: string;
@@ -18,14 +20,41 @@ interface NativeApi { on: (name: 'inbound_claim', handler: (event: NativeEvent,
18
20
  * stay equal. The code starts with a letter and has a digit: ordinary chat never has this shape,
19
21
  * and it never reads as an answer to the batch, which starts with the question number.
20
22
  */
21
- const PULSE_CONSENT = new RegExp("^[ \\t]*(?=[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{0,2}[2-9])[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{3}[ \\t]+[^\\r\\n]{1,40}$", 'i');
23
+ const PULSE_CONSENT = new RegExp("^[ \\t]*(?=[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{0,2}[2-9])[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{3}[ \\t]+[^\\r\\n]{1,200}$", 'i');
22
24
  const PULSE_LOTE = new RegExp("^[ \\t]*[0-9]{1,2}[ \\t]*[a-zA-Z](?:[ \\t,;]*[0-9]{1,2}[ \\t]*[a-zA-Z])*[ \\t]*[.!]?[ \\t]*$");
23
25
  /** I-50 (RM-039): the pulse cadence tag, alone in the message. It starts with '#', so it never reads as the other two. */
24
26
  const PULSE_CADENCIA = new RegExp("^[ \\t]*#OrkPulse(?:On(?:-(?:15|30|60)m)?|Off)[ \\t]*[.!]?[ \\t]*$", 'i');
25
27
  /** The pulse address inside the signed body: neither a thread nor a request. The core translates. */
26
28
  const PULSE_ALVO = 'pulse', PULSE_ENDERECO = 'resposta';
27
- export function isPulseAnswer(text: string): boolean {
28
- return !text.includes('\0') && (PULSE_CONSENT.test(text) || PULSE_LOTE.test(text) || PULSE_CADENCIA.test(text));
29
+ /**
30
+ * RM-048 (D2 and D3): the free-text shapes, generated in the core from the closed vocabulary. `lista`
31
+ * ("1. B, 2. aprovo") is always claimed; `livre` ("sim", "aprovo", "a") only while the core's
32
+ * listening window is open: outside it, the word goes to the assistant, as it always did. The
33
+ * window is a routing hint, not proof: the message still goes through the signature and the core rule.
34
+ */
35
+ const PULSE_LISTA = new RegExp("^[ \\t]*[0-9]{1,2}(?:[ \\t]*[.):-])?[ \\t]*(?:[a-d]|pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?)(?:[ \\t\\r\\n,;]*[0-9]{1,2}(?:[ \\t]*[.):-])?[ \\t]*(?:[a-d]|pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?))*[ \\t]*(?:[.!][ \\t]*)?$", 'i');
36
+ const PULSE_LIVRE = new RegExp("^[ \\t]*(?:[1-4]|(?:[a-d]|pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?))[ \\t]*(?:[.!]+[ \\t]*)?$", 'i');
37
+ /** RM-048 (item 8): the digest keyboard line goes to the core at the same signed address. */
38
+ const PULSE_RATIFICACAO = new RegExp("^[ \\t]*ratificar(?:-lote)?(?:[ \\t]+[A-Za-z0-9._-]{1,80}){2,3}[ \\t]*$", 'i');
39
+ /** Text above this is never tested against the shapes: they run before the allowlist. */
40
+ const PULSE_TETO = 300, ESCUTA_TETO = 4096;
41
+
42
+ /** The bare-word window (ork.pulse-escuta/v1), read with a byte cap; any error closes it. */
43
+ export function listeningOpen(root: string | undefined, now = Date.now()): boolean {
44
+ try {
45
+ if (!root || !isAbsolute(root)) return false;
46
+ const raw = readFileSync(join(root, '.orkastery', 'monitor', 'pulse-escuta.json'));
47
+ if (raw.length > ESCUTA_TETO) return false;
48
+ const value = JSON.parse(raw.toString('utf8'));
49
+ return value?.contrato === 'ork.pulse-escuta/v1' && typeof value.livreAte === 'string' && Date.parse(value.livreAte) > now;
50
+ } catch { return false; }
51
+ }
52
+
53
+ export function isPulseAnswer(text: string, root: string | undefined = process.env.ORK_HITL_ROOT): boolean {
54
+ if (text.includes('\0') || text.length > PULSE_TETO) return false;
55
+ if (PULSE_CONSENT.test(text) || PULSE_LOTE.test(text) || PULSE_CADENCIA.test(text) || PULSE_LISTA.test(text) ||
56
+ PULSE_RATIFICACAO.test(text)) return true;
57
+ return PULSE_LIVRE.test(text) && listeningOpen(root);
29
58
  }
30
59
 
31
60
  export function registerHitlIngress(api: NativeApi, bin: string): void {
@@ -71,6 +71,9 @@ async function rodarOrk(args: string[], signal?: AbortSignal, entrada?: string):
71
71
  if (verifiers) env.ORK_RECEIPT_VERIFIERS = verifiers;
72
72
  // I-36 (D6): o adaptador declara o proprio canal de conducao. Descreve a porta, nao concede autoridade.
73
73
  env.ORK_CANAL = 'openclaw';
74
+ // RM-052 (D3): o cwd do gateway nao e projeto de ninguem. Sem `projeto` pedido, o nucleo usa o unico
75
+ // projeto conhecido da maquina ou devolve a escolha; nunca o manifesto que estiver no cwd do gateway.
76
+ env.ORK_PROJETO_EXPLICITO = '1';
74
77
  return new Promise((resolve) => {
75
78
  const filho = execFile(bin, args, { maxBuffer: MAX_BUFFER, signal, env }, (erro, stdout, stderr) => {
76
79
  const partes = [stdout, stderr].map((t) => (t ?? '').trim()).filter((t) => t !== '');
@@ -154,6 +157,38 @@ function argvDaResposta(p: Record<string, unknown>, alvo: 'gate' | 'sessions'):
154
157
  '--mensagem', `telegram:${chat}:${referencia}`, '--origem', 'telegram'];
155
158
  }
156
159
 
160
+ /**
161
+ * RM-052 (D4): o projeto pedido chega como NOME de projeto registrado (`ork projetos`), nunca como
162
+ * caminho. O modelo escolhe entre os projetos da maquina; nao aponta o nucleo para um diretorio.
163
+ */
164
+ const PADRAO_DO_PROJETO = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
165
+ const PARAMETRO_PROJETO = {
166
+ type: 'string',
167
+ pattern: PADRAO_DO_PROJETO.source,
168
+ description: 'Nome do projeto que o dono pediu (ex.: orkastery), como em `ork projetos`. Sem ele e com mais de um projeto na máquina, a resposta é a escolha; nunca use o diretório do gateway.',
169
+ };
170
+
171
+ /** O `projeto` de uma tool: o padrao que o host confere, a descricao para o modelo e a recusa. */
172
+ interface ProjetoDaTool { padrao: RegExp; descricao: string; recusa: string }
173
+
174
+ const PROJETO_DA_RM052: ProjetoDaTool = {
175
+ padrao: PADRAO_DO_PROJETO,
176
+ descricao: PARAMETRO_PROJETO.description,
177
+ recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery), nunca um caminho',
178
+ };
179
+
180
+ /**
181
+ * RM-054 (fatia 2, D-G4): no roadmap da rede, `projeto` tambem aceita a forja (`github:dono/repo`,
182
+ * `gitlab:grupo/repo`): numa maquina sem clone, e o unico caminho ate o projeto enquanto a rede por
183
+ * pessoa (RM-053) nao chega. Caminho e URL continuam fora (a URL pode levar credencial), e o nucleo
184
+ * confere de novo, porque o adaptador declara ORK_PROJETO_EXPLICITO=1.
185
+ */
186
+ const PROJETO_DA_REDE: ProjetoDaTool = {
187
+ padrao: /^(?:[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}|(?:github|gitlab):[A-Za-z0-9][A-Za-z0-9._-]{0,99}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}){1,8})$/,
188
+ descricao: 'Projeto que o dono pediu: o nome, como em `ork projetos` (ex.: orkastery), ou a forja (github:dono/repo, gitlab:grupo/repo) quando não há clone nesta máquina. Sem ele, vêm todos os projetos conhecidos. Nunca caminho nem URL.',
189
+ recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery) ou github:dono/repo, nunca caminho nem URL',
190
+ };
191
+
157
192
  /** Catálogo em paridade nome a nome com o manifesto; aprovação genérica legada aposentada. */
158
193
  interface FerramentaOrk {
159
194
  name: string;
@@ -161,12 +196,14 @@ interface FerramentaOrk {
161
196
  parameters: Record<string, unknown>;
162
197
  argv: (params: Record<string, unknown>) => string[];
163
198
  entrada?: (params: Record<string, unknown>) => string;
199
+ /** O `projeto` desta tool, quando nao e o da RM-052 (so nome). */
200
+ projeto?: ProjetoDaTool;
164
201
  }
165
202
 
166
203
  const FERRAMENTAS: FerramentaOrk[] = [
167
204
  {
168
205
  name: 'ork_maestro',
169
- description: 'Ao receber a frase exata orkastery maestro, consulte o panorama somente leitura. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. Ação posterior exige operação autorizada e readback.',
206
+ description: 'Ao receber a frase exata orkastery maestro: sem projeto nomeado, chame ork_network_roadmap sem projeto (o panorama da rede, com fontes, frescor e lacunas) e apresente-o como vem; com projeto nomeado, consulte este panorama somente leitura com projeto. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. O panorama NÃO lê o roadmap, as reservas nem as outras máquinas (veja notConsulted): zero threads nunca é roadmap vazio; para o roadmap use ork_network_roadmap. Ação posterior exige operação autorizada e readback.',
170
207
  parameters: { type: 'object', additionalProperties: false, properties: {
171
208
  thread: { type: 'string' }, section: { type: 'string', enum: ['portfolio','demands','threads','sessions','blockers','leases','retries','hitl','ship','master','nextActions'] },
172
209
  offset: { type: 'integer', minimum: 0, maximum: 100000 },
@@ -292,6 +329,29 @@ const FERRAMENTAS: FerramentaOrk[] = [
292
329
  parameters: schema({ thread: { type: 'string' }, id: { type: 'string' } }),
293
330
  argv: (p) => ['brain', 'get', texto(p, 'id'), '--thread', texto(p, 'thread')],
294
331
  },
332
+ {
333
+ name: 'ork_brain_context',
334
+ description:
335
+ 'Pacote de contexto citável do Company Brain: entidades pedidas e seus pais, cada uma com a citação da fonte, o frescor contra o portfólio e as lacunas. Só leitura.',
336
+ parameters: schema({
337
+ thread: { type: 'string' },
338
+ ids: { type: 'string', description: 'ids do portfólio separados por vírgula (prod-, proj-, init-)' },
339
+ }),
340
+ argv: (p) => ['brain', 'context', '--thread', texto(p, 'thread'), '--ids', texto(p, 'ids')],
341
+ },
342
+ {
343
+ name: 'ork_brain_dossie',
344
+ description:
345
+ 'Dossiê de decisão da thread: vínculo com objetivo e projeto, contexto citável, alternativas, decisão, quem decidiu e evidência, com os ids do Company Brain e as lacunas. Resposta do dono só com recibo conferido. Só leitura.',
346
+ parameters: {
347
+ type: 'object', additionalProperties: false, required: ['thread'],
348
+ properties: {
349
+ thread: { type: 'string' },
350
+ decisao: { type: 'string', description: 'id do pedido ou fact- da decisão; omita para a thread inteira' },
351
+ },
352
+ },
353
+ argv: (p) => ['brain', 'dossie', '--thread', texto(p, 'thread'), ...(p.decisao === undefined ? [] : ['--decisao', texto(p, 'decisao')])],
354
+ },
295
355
  {
296
356
  name: 'ork_claims_add',
297
357
  description:
@@ -375,31 +435,35 @@ const FERRAMENTAS: FerramentaOrk[] = [
375
435
  {
376
436
  name: 'ork_master',
377
437
  description:
378
- 'Fecha a thread: POSTMORTEM tipado, MASTER log no contrato congelado e o score HUMANO de 0 a 5 com justificativa. Nunca invente a nota.',
438
+ 'Pede ao dono a nota do MASTER (0 a 5, com o porque) de uma entrega: devolve a linha com o codigo curto para mostrar a ele. A nota volta pelo Telegram, com a prova do ingresso. Esta tool nunca grava nota e nao recebe nome de pessoa (RM-048).',
379
439
  parameters: schema({
380
440
  thread: { type: 'string' },
381
- score: { type: 'string', description: 'inteiro de 0 a 5, dado pelo humano' },
382
- justificativa: { type: 'string' },
383
- quem: { type: 'string' },
384
441
  }),
385
- argv: (p) => [
386
- 'master',
387
- texto(p, 'thread'),
388
- '--score',
389
- texto(p, 'score'),
390
- '--justificativa',
391
- texto(p, 'justificativa'),
392
- '--por',
393
- texto(p, 'quem'),
394
- ],
442
+ argv: (p) => ['master', 'pedir', texto(p, 'thread'), '--formato', 'telegram'],
395
443
  },
396
444
  {
397
445
  name: 'ork_board',
398
446
  description:
399
- 'Todas as threads em uma visao, e o escalonador dizendo quem avanca agora e quem espera.',
447
+ 'As threads DESTE projeto nesta maquina e o escalonador dizendo quem avanca agora e quem espera. NAO le o roadmap: nunca conclua sobre o roadmap a partir do board (zero threads nao e roadmap vazio); para o roadmap use ork_network_roadmap. O cabecalho diz o projeto consultado e o que nao foi lido.',
400
448
  parameters: schema({}),
401
449
  argv: () => ['board', 'plan'],
402
450
  },
451
+ {
452
+ name: 'ork_roadmap_status',
453
+ description:
454
+ 'Status report do roadmap SO desta maquina (o checkout local), no formato aprovado pelo dono (grupos com icones, #HITL e o fecho): nao le as reservas nem as outras maquinas, e o cabecalho diz isso. Para o status do roadmap com as threads de todas as maquinas, as reservas, as fontes e as lacunas, use ork_network_roadmap. Somente leitura: transporte o texto como vem, sem reescrever. Passe projeto com o nome que o dono pediu (ex.: orkastery); sem ele e com mais de um projeto na maquina, a resposta e a escolha. Nunca deduza o roadmap de ork_board ou ork_maestro.',
455
+ parameters: schema({}),
456
+ argv: () => ['roadmap', 'status'],
457
+ },
458
+ {
459
+ name: 'ork_network_roadmap',
460
+ description:
461
+ 'Roadmap da rede, somente leitura: para cada projeto, o status report do roadmap (RM-048) com as threads de TODAS as máquinas, as reservas, as threads por máquina com a idade da batida, a fonte e a hora de cada parte e as lacunas. É a fonte para qualquer pergunta sobre o roadmap ou o status report: transporte o texto como vem, sem reescrever nem resumir. Passe projeto com o nome que o dono pediu (ex.: orkastery) ou github:dono/repo; sem projeto, vêm todos os projetos conhecidos, e esse é o panorama da frase orkastery maestro sem projeto. Lacuna, "não lido" e "Não consultado" são fontes que ficaram sem leitura: nunca conclua "roadmap vazio" nem "nenhuma máquina publicou" a partir delas.',
462
+ parameters: schema({}),
463
+ projeto: PROJETO_DA_REDE,
464
+ // RM-054 (fatia 2): uma chamada de CLI; o `--projeto` vai no inicio e o nucleo o devolve ao `network`.
465
+ argv: () => ['network', 'roadmap'],
466
+ },
403
467
  {
404
468
  name: 'ork_master_batch',
405
469
  description:
@@ -409,20 +473,44 @@ const FERRAMENTAS: FerramentaOrk[] = [
409
473
  },
410
474
  ];
411
475
 
476
+ /** RM-052: toda tool aceita `projeto` opcional, sem mudar o que ela ja exigia. */
477
+ function comProjeto(f: FerramentaOrk): Record<string, unknown> {
478
+ const propriedades = (f.parameters.properties ?? {}) as Record<string, unknown>;
479
+ const p = f.projeto;
480
+ const projeto = p ? { type: 'string', pattern: p.padrao.source, description: p.descricao } : PARAMETRO_PROJETO;
481
+ return { ...f.parameters, properties: { ...propriedades, projeto } };
482
+ }
483
+
484
+ /** `--projeto <nome>` vai no inicio do argv; o resto dos parametros segue para a tool como antes. */
485
+ function argvComProjeto(f: FerramentaOrk, params: Record<string, unknown>): { argv: string[]; resto: Record<string, unknown> } {
486
+ const { projeto, ...resto } = params;
487
+ if (projeto !== undefined && (typeof projeto !== 'string' || !(f.projeto ?? PROJETO_DA_RM052).padrao.test(projeto))) {
488
+ throw new Error('projeto.invalido');
489
+ }
490
+ return { argv: [...(projeto === undefined ? [] : ['--projeto', projeto]), ...f.argv(resto)], resto };
491
+ }
492
+
412
493
  const plugin = defineToolPlugin({
413
494
  id: 'orkastery',
414
495
  name: 'Orkastery',
415
496
  description:
416
- 'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.',
497
+ 'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap.',
417
498
  tools: (tool) =>
418
499
  FERRAMENTAS.map((f) =>
419
500
  tool({
420
501
  name: f.name,
421
502
  description: f.description,
422
- parameters: f.parameters,
503
+ parameters: comProjeto(f),
423
504
  execute: (params, _config, contexto) => {
424
- try { return rodarOrk(f.argv(params), contexto.signal, f.entrada?.(params)); }
425
- catch { return '[ork recusou] resposta humana não confirmada; confira origem e correlação do pedido'; }
505
+ let chamada: ReturnType<typeof argvComProjeto>;
506
+ try { chamada = argvComProjeto(f, params); }
507
+ catch (e) {
508
+ if ((e as Error).message === 'projeto.invalido') {
509
+ return `[ork recusou] projeto.invalido: ${(f.projeto ?? PROJETO_DA_RM052).recusa}`;
510
+ }
511
+ return '[ork recusou] resposta humana não confirmada; confira origem e correlação do pedido';
512
+ }
513
+ return rodarOrk(chamada.argv, contexto.signal, f.entrada?.(chamada.resto));
426
514
  },
427
515
  })
428
516
  ),
@@ -16,7 +16,7 @@ O **roadmap** registra problemas, hipóteses, escolhas, prioridades e andamento
16
16
 
17
17
  | Regra | Aplicação |
18
18
  | --- | --- |
19
- | Identidade | Atribua IDs estáveis, por exemplo `PLAT-01`, `SYS-01`, `MOD-01`, `FEAT-042`, `UC-042-01`, `BR-042-01`, `API-01`, `EVT-01`, `JOB-01` e `RM-042`. Renomear não muda o ID. |
19
+ | Identidade | Atribua IDs estáveis, por exemplo `PLAT-01`, `SYS-01`, `MOD-01`, `FEAT-042`, `UC-042-01`, `BR-042-01`, `API-01`, `EVT-01`, `JOB-01` e `RM-042`. Renomear não muda o ID. No Orkastery, o número da FEAT nova sai de `ork roadmap feat`, reservado entre máquinas; nunca do maior número da sua branch. |
20
20
  | Escopo e estado | Informe se a página descreve `vigente`, `em desenvolvimento`, `proposto` ou `descontinuado`, com ambiente, versão e data de verificação. |
21
21
  | Rastreabilidade | Ligue feature ↔ item de roadmap ↔ casos de uso/regras ↔ contratos/esquemas ↔ código/PR ↔ testes/evidências ↔ release. Use `Não aplicável — motivo` quando uma seção não fizer sentido. |
22
22
  | Fonte | Diferencie `confirmado` (código, contrato, teste ou decisão aprovada), `planejado` (item de roadmap) e `a validar`. Nunca complete uma lacuna por inferência silenciosa. |
@@ -75,7 +75,7 @@ Registre risco, perfis e matriz de permissões (RBAC/ABAC), autenticação/sess
75
75
 
76
76
  ## 4. Modelo de página de feature
77
77
 
78
- Copie [o modelo](../produto/_modelo-feature.md) para cada feature; substitua colchetes por valores ou por `Não aplicável — motivo`. O frontmatter é a camada que agentes e o verificador leem; o corpo é a camada que pessoas leem. Acrescente tabelas de campos e contratos quando houver vários elementos.
78
+ Reserve o número com `ork roadmap feat --thread <thread>` e copie [o modelo](../produto/_modelo-feature.md) para cada feature; substitua colchetes por valores ou por `Não aplicável — motivo`. O frontmatter é a camada que agentes e o verificador leem; o corpo é a camada que pessoas leem. Acrescente tabelas de campos e contratos quando houver vários elementos.
79
79
 
80
80
  Chaves do frontmatter de uma feature:
81
81
 
@@ -80,7 +80,7 @@ Copie [o modelo](../roadmap/_modelo-item.md) para cada item. O frontmatter guard
80
80
 
81
81
  Revisite itens ativos na cadência do time e sempre após decisão, PR relevante, teste, deploy, alteração de flag ou medição de resultado. A cada revisão, compare roadmap, documentação da feature, código, contratos e evidências de produção; anote divergências com owner e prazo. Agentes podem sugerir atualização de status a partir de eventos do repositório, mas um PR mesclado atualiza somente a dimensão **código** até que deploy e exposição sejam comprovados. Registre a data de atualização e mantenha histórico das decisões e mudanças de escopo.
82
82
 
83
- No Orkastery, `ork docs sincronizar` é esse agente: ele atualiza `estado.codigo` e `evidencias.codigo.commit` a partir do ledger (`ship_done`) e do git, e a seção `sdlc` a partir da thread. **Ele nunca mexe em ciclo, documentação, deploy, exposição ou habilitação** — essas dimensões são decisão de pessoa, e o verificador só cobra que estejam coerentes com o código.
83
+ No Orkastery, `ork docs sincronizar` é esse agente: ele atualiza `estado.codigo` e `evidencias.codigo.commit` a partir do ledger (`ship_done`) e do git, e a seção `sdlc` a partir da thread. **Ele nunca mexe em ciclo, documentação, deploy, exposição ou habilitação** — essas dimensões são decisão de pessoa, e o verificador só cobra que estejam coerentes com o código. Na worktree de uma thread, ele só toca o item dela e os índices, para que o PR de uma thread não mude o item de outra; `--so RM-NNN` escolhe os itens, e `--todos` (o padrão na raiz do projeto, onde o condutor sincroniza depois do merge) volta a todos.
84
84
 
85
85
  ## 6. Três leitores e fatos de SDLC (adaptação Orkastery)
86
86
 
@@ -17,6 +17,8 @@ fontes:
17
17
  comandos: []
18
18
  ---
19
19
 
20
+ <!-- O número da feature sai de `ork roadmap feat --thread <thread>`, reservado entre máquinas; nunca do maior número da branch. -->
21
+
20
22
  # FEAT-000 — Nome da feature
21
23
 
22
24
  > **Em uma frase:** [o que esta feature faz, para quem, em até 240 caracteres]