@orkastery/cli 0.4.3 → 0.5.1

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 (146) 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/README.md +11 -2
  4. package/adapters/claude-code/agents/ork-check.md +1 -1
  5. package/adapters/claude-code/agents/ork-go.md +2 -2
  6. package/adapters/claude-code/agents/ork-goal.md +1 -1
  7. package/adapters/claude-code/agents/ork-master.md +1 -1
  8. package/adapters/claude-code/agents/ork-plan.md +1 -1
  9. package/adapters/claude-code/agents/ork-ship.md +1 -1
  10. package/adapters/claude-code/commands/master.md +11 -8
  11. package/adapters/claude-code/commands/onboarding.md +3 -2
  12. package/adapters/claude-code/commands/ork.md +8 -0
  13. package/adapters/claude-code/commands/ship.md +2 -2
  14. package/adapters/codex/skills/ork/SKILL.md +12 -1
  15. package/adapters/hermes/README.md +3 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  17. package/adapters/hermes/bin/ork-brain.sh +3 -0
  18. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  19. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  20. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  21. package/adapters/hermes/hermes.plugin.json +2 -0
  22. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  24. package/adapters/openclaw/README.md +45 -2
  25. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  26. package/adapters/openclaw/dist/index.js +93 -21
  27. package/adapters/openclaw/openclaw.plugin.json +10 -1
  28. package/adapters/openclaw/package.json +2 -2
  29. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  30. package/adapters/openclaw/src/index.ts +109 -21
  31. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  32. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  33. package/assets/docs/produto/_modelo-feature.md +2 -0
  34. package/assets/orkmind_bridge.py +214 -1
  35. package/dist/adapters/claude-bg.js +24 -7
  36. package/dist/auditrun.js +1 -1
  37. package/dist/board.js +74 -0
  38. package/dist/branch-de-estado.js +5 -4
  39. package/dist/busca-semantica.js +161 -0
  40. package/dist/ci.js +141 -10
  41. package/dist/claim-lint.js +3 -1
  42. package/dist/company-brain-cli.js +27 -2
  43. package/dist/company-brain-context.js +132 -0
  44. package/dist/company-brain-dossie.js +376 -0
  45. package/dist/company-brain-mcp.js +4 -1
  46. package/dist/conducao.js +71 -18
  47. package/dist/decisao-autonoma.js +42 -0
  48. package/dist/docs.js +135 -25
  49. package/dist/doctor.js +242 -32
  50. package/dist/entrega-pr.js +116 -5
  51. package/dist/experiencia-instalacao.js +256 -0
  52. package/dist/experiencia.js +72 -0
  53. package/dist/fabrica-estado.js +63 -12
  54. package/dist/fechamento.js +105 -0
  55. package/dist/fix.js +3 -3
  56. package/dist/forja.js +435 -0
  57. package/dist/gates.js +9 -1
  58. package/dist/hitl-classificacao.js +11 -1
  59. package/dist/hitl-contract.js +50 -7
  60. package/dist/hitl-curto.js +158 -0
  61. package/dist/hitl-gates.js +67 -1
  62. package/dist/hitl-lote.js +30 -15
  63. package/dist/hitl-presentation.js +10 -7
  64. package/dist/hitl-resumo.js +57 -13
  65. package/dist/hitl-texto-livre.js +150 -0
  66. package/dist/hosts.js +92 -3
  67. package/dist/index.js +637 -45
  68. package/dist/indice-vetorial.js +317 -0
  69. package/dist/init.js +24 -5
  70. package/dist/integracoes-locais.js +3 -1
  71. package/dist/intelligence-benchmark-contract.js +570 -0
  72. package/dist/intelligence-graph-cli.js +584 -0
  73. package/dist/intelligence-graph-contract.js +636 -0
  74. package/dist/intelligence-graph-extract-md.js +487 -0
  75. package/dist/intelligence-graph-extract-ts.js +980 -0
  76. package/dist/intelligence-graph-extract.js +546 -0
  77. package/dist/intelligence-graph-index.js +693 -0
  78. package/dist/intelligence-graph-parsers.js +327 -0
  79. package/dist/intelligence-graph-query.js +344 -0
  80. package/dist/intelligence-graph-repo.js +197 -0
  81. package/dist/leases.js +100 -0
  82. package/dist/ledger.js +23 -0
  83. package/dist/licoes.js +5 -4
  84. package/dist/liveness.js +2 -1
  85. package/dist/maestro-cli.js +10 -1
  86. package/dist/maestro-contract.js +5 -1
  87. package/dist/maestro-discovery.js +16 -2
  88. package/dist/maestro-runtime.js +3 -0
  89. package/dist/maestro-snapshot.js +6 -2
  90. package/dist/manifest.js +166 -5
  91. package/dist/master-batch.js +3 -2
  92. package/dist/master-digest.js +2 -2
  93. package/dist/master-nota.js +203 -0
  94. package/dist/master.js +4 -0
  95. package/dist/mcp-artifacts.js +2 -0
  96. package/dist/mcp-experiencia.js +164 -0
  97. package/dist/mcp-git.js +64 -12
  98. package/dist/mcp-grafo-worker.js +91 -0
  99. package/dist/mcp-grafo.js +233 -0
  100. package/dist/mcp-install.js +5 -2
  101. package/dist/mcp-maestro.js +3 -2
  102. package/dist/mcp-server.js +93 -5
  103. package/dist/mcp-ship.js +2 -0
  104. package/dist/mcp-verify.js +3 -1
  105. package/dist/memoria.js +147 -1
  106. package/dist/network-roadmap.js +907 -0
  107. package/dist/ocupacao.js +43 -3
  108. package/dist/onboarding.js +122 -11
  109. package/dist/orkmind.js +165 -9
  110. package/dist/orquestracao.js +2 -1
  111. package/dist/parado-no-condutor.js +933 -0
  112. package/dist/phase.js +166 -13
  113. package/dist/policies.js +29 -2
  114. package/dist/projeto-alvo.js +551 -0
  115. package/dist/prompts.js +1 -1
  116. package/dist/prova-ativacao.js +345 -0
  117. package/dist/pulse-consentimento.js +4 -2
  118. package/dist/pulse-delivery.js +32 -6
  119. package/dist/pulse-resposta.js +674 -49
  120. package/dist/pulse.js +74 -3
  121. package/dist/retry.js +51 -8
  122. package/dist/roadmap-reservas.js +262 -20
  123. package/dist/roadmap-status.js +348 -0
  124. package/dist/runtime-context.js +5 -1
  125. package/dist/sessoes-inventario.js +7 -1
  126. package/dist/sessoes.js +4 -1
  127. package/dist/ship.js +25 -1
  128. package/dist/thread-close.js +3 -0
  129. package/dist/thread.js +21 -2
  130. package/dist/util.js +71 -4
  131. package/dist/verify.js +6 -5
  132. package/dist/worktree.js +29 -6
  133. package/eval/casos/onboarding.json +1 -1
  134. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  135. package/eval/casos/orchestration-experience.json +48 -0
  136. package/package.json +1 -1
  137. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  138. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  139. package/schemas/maestro-snapshot.schema.json +23 -0
  140. package/skills/README.md +7 -5
  141. package/skills/core/onboarding/SKILL.md +10 -2
  142. package/skills/core/orchestration-experience/SKILL.md +113 -0
  143. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  144. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  145. package/skills/phases/go-implementation/SKILL.md +2 -0
  146. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ENTRADAS = exports.HOSTS_DA_PROVA = void 0;
37
+ exports.transcriptDoClaude = transcriptDoClaude;
38
+ exports.transcriptDoOpenclaw = transcriptDoOpenclaw;
39
+ exports.extrairSnapshot = extrairSnapshot;
40
+ exports.conferirProva = conferirProva;
41
+ exports.redigir = redigir;
42
+ exports.redigirObjeto = redigirObjeto;
43
+ /**
44
+ * RM-032: conferência da prova de ativação por host, sem host.
45
+ *
46
+ * O roteiro `core/scripts/prova-ativacao.cjs` abre uma sessão nova e não interativa no host
47
+ * (Claude Code ou OpenClaw), diz `orkastery maestro` e entrega aqui o transcript. Esta
48
+ * conferência olha só o que é determinístico: qual ferramenta o modelo chamou, o resultado que
49
+ * ela devolveu (contra o contrato da entrada) e o projeto lido. Da resposta final em texto
50
+ * livre, exige apenas que cite o projeto e não repita o incidente de 29/09 ("roadmap vazio").
51
+ */
52
+ const fs = __importStar(require("node:fs"));
53
+ const path = __importStar(require("node:path"));
54
+ const maestro_contract_1 = require("./maestro-contract");
55
+ exports.HOSTS_DA_PROVA = ['claude-code', 'openclaw'];
56
+ /**
57
+ * As entradas da frase em cada host. No Claude, a tool MCP do servidor `orkastery` fixado no
58
+ * projeto (MAESTRO_HOST_SURFACES em hosts.ts). No OpenClaw 0.5.0 (RM-054, fatia 2), a frase sem
59
+ * projeto vai a `ork_network_roadmap`, a única tool da extensão no perfil `coding`; com projeto
60
+ * nomeado, a `ork_maestro`. `ork maestro` pelo shell do host é desvio: o CLI resolve o projeto
61
+ * pelo diretório atual, a classe de erro do incidente.
62
+ */
63
+ exports.ENTRADAS = {
64
+ 'claude-code': [{ ferramenta: 'mcp__orkastery__ork_maestro', contrato: 'maestro' }],
65
+ openclaw: [{ ferramenta: 'ork_network_roadmap', contrato: 'rede' }, { ferramenta: 'ork_maestro', contrato: 'maestro' }],
66
+ };
67
+ const SHELL_DO_HOST = { 'claude-code': 'Bash', openclaw: 'exec' };
68
+ const ORK_MAESTRO_NO_SHELL = /(^|[\s;&|(/])ork(\s+(--projeto|--project|-p)(\s+|=)\S+)?\s+maestro\b/;
69
+ function linhasJson(texto) {
70
+ const eventos = [];
71
+ for (const linha of texto.split('\n')) {
72
+ const limpa = linha.trim();
73
+ if (!limpa.startsWith('{'))
74
+ continue;
75
+ try {
76
+ eventos.push(JSON.parse(limpa));
77
+ }
78
+ catch { /* linha de log do host, não evento */ }
79
+ }
80
+ return eventos;
81
+ }
82
+ function textoDoConteudo(conteudo) {
83
+ if (typeof conteudo === 'string')
84
+ return conteudo;
85
+ if (!Array.isArray(conteudo))
86
+ return null;
87
+ const partes = conteudo.filter((c) => !!c && typeof c === 'object' && c.type === 'text' && typeof c.text === 'string');
88
+ return partes.length ? partes.map(p => p.text).join('\n') : null;
89
+ }
90
+ function classificar(host, ferramenta, argumentos) {
91
+ const entrada = exports.ENTRADAS[host].find(e => e.ferramenta === ferramenta);
92
+ if (entrada)
93
+ return { via: 'entrada', contrato: entrada.contrato };
94
+ const comando = argumentos?.command;
95
+ if (ferramenta === SHELL_DO_HOST[host] && typeof comando === 'string' && ORK_MAESTRO_NO_SHELL.test(comando))
96
+ return { via: 'shell', contrato: null };
97
+ return null;
98
+ }
99
+ /** Transcript `claude -p --output-format stream-json --verbose`. */
100
+ function transcriptDoClaude(streamJson) {
101
+ const eventos = linhasJson(streamJson);
102
+ let ferramentasExpostas = null;
103
+ let respostaFinal = null;
104
+ const pedidas = new Map();
105
+ const chamadas = [];
106
+ for (const e of eventos) {
107
+ if (e.type === 'system' && e.subtype === 'init' && Array.isArray(e.tools))
108
+ ferramentasExpostas = e.tools.filter((t) => typeof t === 'string');
109
+ const mensagem = e.message;
110
+ const blocos = Array.isArray(mensagem?.content) ? mensagem.content : [];
111
+ if (e.type === 'assistant') {
112
+ for (const b of blocos) {
113
+ if (b?.type !== 'tool_use' || typeof b.name !== 'string' || typeof b.id !== 'string')
114
+ continue;
115
+ const tipo = classificar('claude-code', b.name, b.input);
116
+ if (!tipo)
117
+ continue;
118
+ const chamada = { ferramenta: b.name, ...tipo, argumentos: b.input ?? null, resultado: null, erro: false };
119
+ pedidas.set(b.id, chamada);
120
+ chamadas.push(chamada);
121
+ }
122
+ }
123
+ else if (e.type === 'user') {
124
+ for (const b of blocos) {
125
+ if (b?.type !== 'tool_result' || typeof b.tool_use_id !== 'string')
126
+ continue;
127
+ const chamada = pedidas.get(b.tool_use_id);
128
+ if (!chamada)
129
+ continue;
130
+ chamada.resultado = textoDoConteudo(b.content);
131
+ chamada.erro = b.is_error === true;
132
+ }
133
+ }
134
+ else if (e.type === 'result' && typeof e.result === 'string')
135
+ respostaFinal = e.result;
136
+ }
137
+ return { ferramentasExpostas, chamadas, respostaFinal };
138
+ }
139
+ /**
140
+ * Trajetória do OpenClaw (`openclaw sessions export-trajectory`, `events.jsonl`): eventos
141
+ * `tool.call`/`tool.result` da origem `runtime`. As ferramentas expostas e a resposta final vêm
142
+ * do `openclaw agent --json` (`meta.systemPromptReport.tools.entries`, `payloads[].text`).
143
+ */
144
+ function transcriptDoOpenclaw(eventsJsonl, agenteJson) {
145
+ const pedidas = new Map();
146
+ const chamadas = [];
147
+ for (const e of linhasJson(eventsJsonl)) {
148
+ if (e.source !== 'runtime')
149
+ continue;
150
+ const d = (e.data ?? {});
151
+ if (typeof d.toolCallId !== 'string' || typeof d.name !== 'string')
152
+ continue;
153
+ if (e.type === 'tool.call') {
154
+ const tipo = classificar('openclaw', d.name, d.args);
155
+ if (!tipo)
156
+ continue;
157
+ const chamada = { ferramenta: d.name, ...tipo, argumentos: d.args ?? null, resultado: null, erro: false };
158
+ pedidas.set(d.toolCallId, chamada);
159
+ chamadas.push(chamada);
160
+ }
161
+ else if (e.type === 'tool.result') {
162
+ const chamada = pedidas.get(d.toolCallId);
163
+ if (!chamada)
164
+ continue;
165
+ chamada.resultado = textoDoConteudo(d.result?.content);
166
+ chamada.erro = d.success !== true;
167
+ }
168
+ }
169
+ const agente = (agenteJson ?? {});
170
+ const entradas = agente.meta?.systemPromptReport?.tools?.entries;
171
+ const ferramentasExpostas = Array.isArray(entradas) ? entradas.map(t => t?.name).filter((n) => typeof n === 'string') : null;
172
+ const textos = (agente.payloads ?? []).map(p => p?.text).filter((t) => typeof t === 'string');
173
+ return { ferramentasExpostas, chamadas, respostaFinal: textos.length ? textos.join('\n') : null };
174
+ }
175
+ /** O primeiro objeto `ork.maestro-snapshot/v1` do texto (o host pode juntar stderr ao stdout). */
176
+ function extrairSnapshot(texto) {
177
+ const inicio = texto.indexOf('{"schema":"ork.maestro-snapshot/v1"');
178
+ if (inicio < 0)
179
+ return null;
180
+ let profundidade = 0, emTexto = false, escape = false;
181
+ for (let i = inicio; i < texto.length; i++) {
182
+ const c = texto[i];
183
+ if (emTexto) {
184
+ if (escape)
185
+ escape = false;
186
+ else if (c === '\\')
187
+ escape = true;
188
+ else if (c === '"')
189
+ emTexto = false;
190
+ }
191
+ else if (c === '"')
192
+ emTexto = true;
193
+ else if (c === '{')
194
+ profundidade++;
195
+ else if (c === '}' && --profundidade === 0) {
196
+ try {
197
+ return JSON.parse(texto.slice(inicio, i + 1));
198
+ }
199
+ catch {
200
+ return null;
201
+ }
202
+ }
203
+ }
204
+ return null;
205
+ }
206
+ function expandirHome(raiz, home) {
207
+ return raiz === '~' ? home : raiz.startsWith('~/') ? path.join(home, raiz.slice(2)) : raiz;
208
+ }
209
+ const ROADMAP_VAZIO = /\broadmap\b:?((?:\s+[^\s.!?:;,]+){0,4}?)\s+(vazio|empty)\b/giu;
210
+ const NEGACAO = /(^|[^\p{L}])(n[ãa]o|nunca|jamais|not|never|isn't|doesn't|nem)([^\p{L}]|$)/iu;
211
+ /**
212
+ * A frase que conclui "roadmap vazio". A negação só conta quando governa a conclusão: dentro do
213
+ * trecho ("the roadmap is not empty") ou nas até seis palavras antes dele, sem atravessar
214
+ * pontuação ("zero threads não quer dizer roadmap vazio"). "O roadmap está vazio e não há
215
+ * threads" e "Não há itens: o roadmap está vazio" concluem.
216
+ */
217
+ function concluiRoadmapVazio(texto) {
218
+ for (const m of texto.matchAll(ROADMAP_VAZIO)) {
219
+ const antes = texto.slice(0, m.index).split(/[.!?:;,\n]/).pop().trim().split(/\s+/).slice(-6).join(' ');
220
+ if (!NEGACAO.test(antes) && !NEGACAO.test(m[1]))
221
+ return `${antes} ${m[0]}`.trim();
222
+ }
223
+ return null;
224
+ }
225
+ const canonica = (p) => { try {
226
+ return fs.realpathSync(p);
227
+ }
228
+ catch {
229
+ return path.resolve(p);
230
+ } };
231
+ /** Nome inteiro, não pedaço de palavra: a abreviação `pav` não casa com "pavimento". */
232
+ const nomeia = (texto, nome) => new RegExp(`(^|[^\\p{L}\\p{N}_-])${nome.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\p{L}\\p{N}_-]|$)`, 'iu').test(texto);
233
+ /**
234
+ * O texto de `ork network roadmap` (`textoDoPanoramaDaRede`): a primeira linha diz de onde foi
235
+ * lido, a segunda quais projetos foram consultados (`<nome> (clone em <raiz>)`) e o bloco
236
+ * "Não consultado:" o que ficou de fora.
237
+ */
238
+ function lerPanoramaDaRede(texto) {
239
+ const linhas = texto.split('\n');
240
+ const i = linhas.findIndex(l => /^Panorama da rede lido de /.test(l));
241
+ if (i < 0 || !/^Consultado: /.test(linhas[i + 1] ?? '') || linhas[i + 2] !== 'Não consultado:')
242
+ return null;
243
+ const naoConsultado = [];
244
+ for (const l of linhas.slice(i + 3)) {
245
+ if (!l.startsWith('• '))
246
+ break;
247
+ naoConsultado.push(l.slice(2));
248
+ }
249
+ return { cabecalho: linhas[i], consultado: linhas[i + 1], naoConsultado };
250
+ }
251
+ function conferirProva(host, t, esperado) {
252
+ const conferencias = [];
253
+ const conferir = (id, ok, detalhe) => { conferencias.push({ id, ok, detalhe }); return ok; };
254
+ const nomes = exports.ENTRADAS[host].map(e => e.ferramenta);
255
+ const expostas = nomes.filter(n => t.ferramentasExpostas?.includes(n));
256
+ conferir('entrada.exposta', expostas.length > 0, t.ferramentasExpostas === null ? 'o transcript não lista as ferramentas expostas'
257
+ : expostas.length ? `${expostas.join(', ')} exposta(s) ao modelo`
258
+ : `${nomes.join(' e ')} ausente(s) entre ${t.ferramentasExpostas.length} ferramentas expostas`);
259
+ // A última chamada contratada com resultado é a que vale (uma nova tentativa depois de erro não
260
+ // reprova); qualquer `ork maestro` pelo shell reprova, mesmo ao lado da chamada contratada.
261
+ const contratadas = t.chamadas.filter(c => c.via === 'entrada');
262
+ const contratada = [...contratadas].reverse().find(c => c.resultado !== null && !c.erro) ?? contratadas.at(-1);
263
+ const desvio = t.chamadas.find(c => c.via === 'shell');
264
+ const comando = desvio && JSON.stringify(desvio.argumentos?.command);
265
+ conferir('entrada.chamada', !!contratada && !desvio, contratada && desvio ? `${contratada.ferramenta} chamada, mas também desvio: ${desvio.ferramenta} ${comando}`
266
+ : contratada ? `${contratada.ferramenta} chamada`
267
+ : desvio ? `desvio: ${desvio.ferramenta} ${comando} em vez de ${nomes.join(' ou ')}`
268
+ : `${nomes.join(' ou ')} não foi chamada`);
269
+ let snapshot = null;
270
+ let rede = null;
271
+ if (contratada && conferir('resultado.sem-erro', contratada.resultado !== null && !contratada.erro, contratada.resultado === null ? 'a chamada não teve resultado' : contratada.erro ? `erro: ${contratada.resultado.slice(0, 300)}` : 'resultado sem erro')) {
272
+ if (contratada.contrato === 'maestro') {
273
+ try {
274
+ snapshot = (0, maestro_contract_1.validateMaestroSnapshot)(extrairSnapshot(contratada.resultado));
275
+ conferir('resultado.contrato', true, `${snapshot.schema} validado`);
276
+ }
277
+ catch (e) {
278
+ conferir('resultado.contrato', false, `fora do contrato: ${e.message.slice(0, 300)}`);
279
+ }
280
+ }
281
+ else {
282
+ rede = lerPanoramaDaRede(contratada.resultado);
283
+ conferir('resultado.contrato', rede !== null, rede ? 'texto do ork.network-roadmap/v1: cabeçalho, Consultado e Não consultado'
284
+ : 'fora do contrato: sem o cabeçalho "Panorama da rede lido de", "Consultado:" e "Não consultado:"');
285
+ }
286
+ }
287
+ if (snapshot) {
288
+ const p = snapshot.project;
289
+ const exibida = p.root === undefined || p.root.includes('[caminho privado]') ? null : canonica(expandirHome(p.root, esperado.home));
290
+ const mesmaCopia = p.fingerprint === esperado.projeto.fingerprint && (exibida === null || exibida === canonica(esperado.projeto.raiz));
291
+ conferir('resultado.projeto', p.name === esperado.projeto.nome && mesmaCopia, `leu ${p.name} (${p.origin}) em ${p.root ?? 'raiz não exibida'}, impressão ${p.fingerprint.slice(0, 12)}; ` +
292
+ `esperado ${esperado.projeto.nome}, impressão ${esperado.projeto.fingerprint.slice(0, 12)}`);
293
+ const lacunas = snapshot.notConsulted ?? [];
294
+ conferir('resultado.nao-consultado', lacunas.length > 0, lacunas.length ? `declara o que não leu: ${lacunas.length} item(ns)` : 'notConsulted ausente: zero threads pode virar "roadmap vazio"');
295
+ }
296
+ if (rede) {
297
+ // "Consultado: a (clone em X); b (clone em Y)": vale o projeto esperado na raiz esperada.
298
+ const lidos = [...rede.consultado.replace(/^Consultado: /, '').matchAll(/(?:^|; )(.+?) \(clone em ([^)]+)\)/g)];
299
+ const leu = lidos.some(([, nome, raiz]) => nome === esperado.projeto.nome &&
300
+ canonica(expandirHome(raiz, esperado.home)) === canonica(esperado.projeto.raiz));
301
+ conferir('resultado.projeto', leu, `${rede.consultado.slice(0, 300)}; esperado ${esperado.projeto.nome} (clone em ${esperado.projeto.raiz})`);
302
+ conferir('resultado.nao-consultado', rede.naoConsultado.length > 0, rede.naoConsultado.length ? `declara o que não leu: ${rede.naoConsultado.length} item(ns)` : 'sem o bloco do que não foi consultado');
303
+ }
304
+ const resposta = t.respostaFinal ?? '';
305
+ const citaProjeto = [esperado.projeto.nome, esperado.projeto.id].some(n => nomeia(resposta, n));
306
+ conferir('resposta.cita-projeto', citaProjeto, t.respostaFinal === null ? 'sem resposta final' : citaProjeto ? 'a resposta nomeia o projeto lido' : 'a resposta não nomeia o projeto lido');
307
+ const vazio = concluiRoadmapVazio(resposta);
308
+ conferir('resposta.sem-roadmap-vazio', vazio === null, vazio ? `a resposta conclui "roadmap vazio" a partir do panorama (incidente de 29/09): ${vazio.slice(0, 200)}` : 'sem concluir "roadmap vazio" a partir do panorama');
309
+ return {
310
+ ok: conferencias.every(c => c.ok),
311
+ conferencias,
312
+ snapshot: snapshot && {
313
+ schema: snapshot.schema, projeto: snapshot.project,
314
+ secoes: Object.fromEntries(Object.entries(snapshot.sections).map(([nome, s]) => [nome, s.state])),
315
+ notConsulted: snapshot.notConsulted ?? [],
316
+ },
317
+ rede,
318
+ };
319
+ }
320
+ /**
321
+ * Redação antes de qualquer gravação: o recibo vai ao repositório. Cobre tokens com prefixo
322
+ * conhecido, `Bearer`/`Basic`, variáveis de ambiente com nome de segredo e valores de chaves JSON
323
+ * com nome de segredo. Para objetos, use `redigirObjeto`, que aplica a regra a cada texto antes
324
+ * da serialização (JSON dentro de stdout escaparia depois dela).
325
+ */
326
+ const CHAVE_DE_SEGREDO = /token|secret|password|passwd|api[_-]?key|apikey|credential|authorization|cookie/i;
327
+ function redigir(texto) {
328
+ return texto
329
+ .replace(/\b(sk-ant-[A-Za-z0-9_-]+|sk-or-[A-Za-z0-9_-]+|sk-[A-Za-z0-9_-]{16,}|[sr]k_live_[A-Za-z0-9]{10,}|AIza[0-9A-Za-z_-]{20,})/g, '[REDIGIDO]')
330
+ .replace(/\b(gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{16,}|xox[abprs]-[A-Za-z0-9-]{10,})/g, '[REDIGIDO]')
331
+ .replace(/\b(Bearer|Basic)(\s+)[A-Za-z0-9._~+/-]{8,}=*/gi, '$1$2[REDIGIDO]')
332
+ // O valor para em aspas, barra invertida e espaço: aplicada a JSON, a regra não come o fecho.
333
+ .replace(/\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*\s*=\s*)(?!\d+\b)("[^"\\]*"|'[^'\\]*'|[^\s"'\\]+)/g, '$1[REDIGIDO]')
334
+ .replace(/("[^"]*(?:token|secret|password|passwd|api[_-]?key|apikey|credential|authorization|cookie)[^"]*"\s*:\s*)"(?:[^"\\]|\\.)*"/gi, '$1"[REDIGIDO]"');
335
+ }
336
+ function redigirObjeto(valor) {
337
+ if (typeof valor === 'string')
338
+ return redigir(valor);
339
+ if (Array.isArray(valor))
340
+ return valor.map(v => redigirObjeto(v));
341
+ if (valor && typeof valor === 'object') {
342
+ return Object.fromEntries(Object.entries(valor).map(([k, v]) => [k, typeof v === 'string' && CHAVE_DE_SEGREDO.test(k) ? '[REDIGIDO]' : redigirObjeto(v)]));
343
+ }
344
+ return valor;
345
+ }
@@ -72,6 +72,7 @@ const path = __importStar(require("node:path"));
72
72
  const node_crypto_1 = require("node:crypto");
73
73
  const estado_thread_1 = require("./estado-thread");
74
74
  const hitl_gates_1 = require("./hitl-gates");
75
+ const hitl_texto_livre_1 = require("./hitl-texto-livre");
75
76
  exports.CONTRATO_CONSENTIMENTO = 'ork.pulse-consent/v1';
76
77
  /**
77
78
  * Crockford base32 sem os quatro ambiguos (I, L, O, U). Com letra na frente e ao menos um digito,
@@ -140,7 +141,8 @@ function interpretarResposta(bruto, codigo) {
140
141
  return 'sim';
141
142
  if (['b', 'n', 'nao', 'não', 'agora nao', 'agora não'].includes(corpo))
142
143
  return 'nao';
143
- return undefined;
144
+ // RM-048 (D2): o mesmo vocabulario fechado do texto livre ("pode seguir", "aprovo", "depois").
145
+ return (0, hitl_texto_livre_1.consentimentoDoTrecho)((0, hitl_texto_livre_1.lerTrecho)(corpo));
144
146
  }
145
147
  /**
146
148
  * A assinatura de um conjunto de candidatos: os mesmos gates, a mesma oferta. O pedido aberto nao
@@ -234,7 +236,7 @@ function responderConsentimento(raiz, entrada) {
234
236
  }
235
237
  return { resposta: estado.respondido.resposta, repetida: true, pedido, respondido: estado.respondido };
236
238
  }
237
- if (Date.parse(quando) >= Date.parse(pedido.prazo)) {
239
+ if (Date.parse(quando) >= Date.parse(pedido.prazo) && entrada.aceitarVencido !== true) {
238
240
  throw new Error('consentimento do pulse: pedido vencido; nenhuma autorização concedida');
239
241
  }
240
242
  if (!resposta)
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.adquirirLockMonitor = void 0;
36
+ exports.RETEM_PARADO_MS = exports.adquirirLockMonitor = void 0;
37
37
  exports.assinaturaPulse = assinaturaPulse;
38
38
  exports.assinaturaDoResumo = assinaturaDoResumo;
39
39
  exports.mensagemPulse = mensagemPulse;
@@ -63,6 +63,8 @@ const pulse_cadencia_1 = require("./pulse-cadencia");
63
63
  // O lock dos monitores mudou de modulo (I-41, GO-FIX 1); quem o importava daqui continua importando.
64
64
  var monitor_lock_2 = require("./monitor-lock");
65
65
  Object.defineProperty(exports, "adquirirLockMonitor", { enumerable: true, get: function () { return monitor_lock_2.adquirirLockMonitor; } });
66
+ /** RM-037 (fatia 4): por quanto tempo a parada ja informada continua sabida depois de sumir de uma batida. */
67
+ exports.RETEM_PARADO_MS = 6 * 60 * 60 * 1000;
66
68
  /**
67
69
  * I-41 (D9): a assinatura de deduplicacao NAO inclui `apresentacao`.
68
70
  *
@@ -183,9 +185,24 @@ function varrerPulse(opcoes) {
183
185
  const esperasFora = outras.flatMap(m => m.esperando.map(e => `maquina:${m.maquina}:${e.thread}:${e.fase}`));
184
186
  for (const k of esperasFora)
185
187
  presentes.add(k);
186
- vistas = Object.fromEntries(Object.entries(vistas).filter(([id]) => presentes.has(id)));
188
+ // RM-037 (fatia 4, D7): o trabalho parado no condutor e noticia uma vez por thread e caso, como a
189
+ // decisao informada: sai na janela da cadencia do dono e nunca abre pedido.
190
+ const parados = pulse.paradoNoCondutor ?? [];
191
+ const chaveDoParado = (p) => `parado:${p.thread}:${p.caso}`;
192
+ for (const p of parados)
193
+ presentes.add(chaveDoParado(p));
194
+ // A parada que some por uma batida (gh que falhou, um `ork verify` do condutor) e volta nao e noticia
195
+ // nova: a marca fica guardada por `RETEM_PARADO_MS` depois da ultima vez que a parada apareceu.
196
+ const guardada = (id, valor) => id.startsWith('parado:') &&
197
+ Date.parse(quando) - Number(valor.split('|')[1]) < exports.RETEM_PARADO_MS;
198
+ vistas = Object.fromEntries(Object.entries(vistas).filter(([id, valor]) => presentes.has(id) || guardada(id, valor)));
187
199
  const esperaNovaFora = esperasFora.some(k => vistas[k] === undefined);
188
200
  const decisoesNovas = decisoes.filter(d => vistas[chaveDaDecisao(d.id)] === undefined);
201
+ const paradosNovos = parados.filter(p => vistas[chaveDoParado(p)] === undefined);
202
+ // A marca guarda o instante em milissegundos: e dado de maquina, nao texto para o dono.
203
+ for (const p of parados)
204
+ if (vistas[chaveDoParado(p)] !== undefined)
205
+ vistas[chaveDoParado(p)] = `informada|${Date.parse(quando)}`;
189
206
  // I-41 (GO-FIX 1): a thread que ja recebeu a pergunta e ainda nao respondeu nao e noticia. A
190
207
  // mudanca no item dela (o pedido que o sim do dono abriu) e obra nossa, e avisar sobre ela seria
191
208
  // mandar ao dono um resumo sobre a pergunta que ele acabou de receber.
@@ -205,7 +222,8 @@ function varrerPulse(opcoes) {
205
222
  !config.argumentos.every(a => typeof a === 'string') || !config.argumentos.some(a => a.includes('{{mensagem}}'))) {
206
223
  throw new Error('transporte requer executavel e argumentos com {{mensagem}}');
207
224
  }
208
- const r = (0, node_child_process_1.spawnSync)(config.executavel, config.argumentos.map(a => a.replaceAll('{{mensagem}}', mensagem)), { cwd: raiz, encoding: 'utf8', input: mensagem, timeout: 30000, maxBuffer: 1024 * 1024 });
225
+ // A mensagem entra como texto: `$&` e `$'` nela nao sao padrao de substituicao (CHECK da RM-037, fatia 4).
226
+ const r = (0, node_child_process_1.spawnSync)(config.executavel, config.argumentos.map(a => a.replaceAll('{{mensagem}}', () => mensagem)), { cwd: raiz, encoding: 'utf8', input: mensagem, timeout: 30000, maxBuffer: 1024 * 1024 });
209
227
  entregue = r.status === 0;
210
228
  }
211
229
  if (!entregue)
@@ -229,17 +247,23 @@ function varrerPulse(opcoes) {
229
247
  // I-50: pergunta nova sai na hora; o resto (status, decisoes informadas) espera a janela da
230
248
  // cadencia do dono, e a novidade que esperou continua nao vista ate sair.
231
249
  const noPrazo = !opcoes.comCadencia || (0, pulse_cadencia_1.janelaAberta)(cadencia, (0, pulse_cadencia_1.lerUltimoResumo)(opcoes.raiz, dir), quando);
232
- const adiada = !conjuntoNovo && !esperaNovaFora && (novas > 0 || decisoesNovas.length > 0) && !noPrazo;
233
- if (conjuntoNovo || esperaNovaFora || (noPrazo && (novas > 0 || decisoesNovas.length > 0))) {
250
+ const informacaoNova = novas > 0 || decisoesNovas.length > 0 || paradosNovos.length > 0;
251
+ const adiada = !conjuntoNovo && !esperaNovaFora && informacaoNova && !noPrazo;
252
+ if (conjuntoNovo || esperaNovaFora || (noPrazo && informacaoNova)) {
234
253
  const guardadas = anterior ? fila.candidatos.filter(c => anterior.candidatos.some(a => a.thread === c.thread)).length : 0;
235
254
  const resumo = (0, hitl_resumo_1.resumirHitl)(itens, { quando, janelaMin, prontas: fila.candidatos.length, acumuladas: guardadas,
236
255
  consertos: fila.consertos, atoDoItem: i => fila.atos.get(`${i.thread}|${i.fase}`),
237
- decisoes: decisoesNovas, acimaDoLimiar: pulse.acimaDoLimiar ?? [], outrasMaquinas: outras });
256
+ decisoes: decisoesNovas, acimaDoLimiar: pulse.acimaDoLimiar ?? [], outrasMaquinas: outras, paradosNoCondutor: parados });
238
257
  // Pedir licenca para mandar zero perguntas era o defeito: sem pergunta, nao ha codigo.
239
258
  // I-50: cadencia curta nao encurta o prazo do dono para responder ao resumo (minimo de 60 min).
240
259
  const consentimento = fila.candidatos.length ? (0, monitor_lock_1.comLockDaConversa)(dir, () => (0, pulse_consentimento_1.abrirConsentimento)(raiz, { quando,
241
260
  resumoSha256: assinaturaDoResumo(resumo), candidatos: fila.candidatos, prazoMin: Math.max(janelaMin, pulse_consentimento_1.PRAZO_PADRAO_MIN), estadoDir: dir })) : undefined;
242
261
  entregar((0, hitl_resumo_1.textoDoResumo)(resumo, { canal, codigo: consentimento?.codigo }), 'o resumo');
262
+ // RM-048 (D3): o resumo acabou de sair; a palavra solta ("sim") vale na janela curta dele.
263
+ try {
264
+ (0, pulse_resposta_1.atualizarEscuta)(raiz, quando, dir);
265
+ }
266
+ catch { /* dica de rota; a prova nao depende dela */ }
243
267
  // Marcado so depois do recibo do transporte: envio que falhou preserva a novidade.
244
268
  for (const item of itens)
245
269
  vistas[item.id] = assinaturaPulse(item);
@@ -247,6 +271,8 @@ function varrerPulse(opcoes) {
247
271
  vistas[chaveDaDecisao(d.id)] = 'informada';
248
272
  for (const k of esperasFora)
249
273
  vistas[k] = 'informada';
274
+ for (const p of parados)
275
+ vistas[chaveDoParado(p)] = `informada|${Date.parse(quando)}`;
250
276
  (0, pulse_cadencia_1.gravarUltimoResumo)(opcoes.raiz, quando, dir);
251
277
  }
252
278
  gravarCache(cache, vistas);