@orkastery/cli 0.5.0 → 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 (63) hide show
  1. package/adapters/claude-code/README.md +11 -2
  2. package/adapters/claude-code/commands/onboarding.md +3 -2
  3. package/adapters/openclaw/README.md +11 -0
  4. package/adapters/openclaw/dist/index.js +1 -1
  5. package/adapters/openclaw/openclaw.plugin.json +1 -1
  6. package/adapters/openclaw/package.json +1 -1
  7. package/adapters/openclaw/src/index.ts +1 -1
  8. package/dist/adapters/claude-bg.js +20 -6
  9. package/dist/auditrun.js +1 -1
  10. package/dist/claim-lint.js +3 -1
  11. package/dist/docs.js +71 -3
  12. package/dist/doctor.js +209 -32
  13. package/dist/entrega-pr.js +7 -0
  14. package/dist/fabrica-estado.js +21 -3
  15. package/dist/fix.js +3 -3
  16. package/dist/hitl-resumo.js +24 -1
  17. package/dist/hosts.js +7 -3
  18. package/dist/index.js +42 -14
  19. package/dist/init.js +11 -5
  20. package/dist/integracoes-locais.js +2 -0
  21. package/dist/intelligence-graph-cli.js +181 -27
  22. package/dist/intelligence-graph-contract.js +71 -12
  23. package/dist/intelligence-graph-extract-md.js +41 -23
  24. package/dist/intelligence-graph-extract-ts.js +181 -11
  25. package/dist/intelligence-graph-extract.js +186 -9
  26. package/dist/intelligence-graph-index.js +205 -24
  27. package/dist/intelligence-graph-repo.js +10 -0
  28. package/dist/leases.js +16 -0
  29. package/dist/ledger.js +9 -0
  30. package/dist/licoes.js +5 -4
  31. package/dist/liveness.js +2 -1
  32. package/dist/maestro-runtime.js +3 -0
  33. package/dist/manifest.js +24 -0
  34. package/dist/mcp-artifacts.js +2 -0
  35. package/dist/mcp-git.js +60 -12
  36. package/dist/mcp-grafo-worker.js +91 -0
  37. package/dist/mcp-grafo.js +233 -0
  38. package/dist/mcp-install.js +2 -1
  39. package/dist/mcp-server.js +9 -0
  40. package/dist/mcp-ship.js +2 -0
  41. package/dist/mcp-verify.js +3 -1
  42. package/dist/network-roadmap.js +5 -5
  43. package/dist/ocupacao.js +6 -2
  44. package/dist/onboarding.js +9 -4
  45. package/dist/orquestracao.js +2 -1
  46. package/dist/parado-no-condutor.js +933 -0
  47. package/dist/policies.js +27 -2
  48. package/dist/prova-ativacao.js +345 -0
  49. package/dist/pulse-delivery.js +27 -6
  50. package/dist/pulse.js +53 -3
  51. package/dist/roadmap-status.js +142 -7
  52. package/dist/runtime-context.js +5 -1
  53. package/dist/sessoes-inventario.js +7 -1
  54. package/dist/sessoes.js +4 -1
  55. package/dist/ship.js +25 -1
  56. package/dist/thread.js +21 -2
  57. package/dist/util.js +71 -4
  58. package/dist/verify.js +2 -2
  59. package/dist/worktree.js +29 -6
  60. package/eval/casos/onboarding.json +1 -1
  61. package/package.json +1 -1
  62. package/skills/core/onboarding/SKILL.md +3 -2
  63. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.USO_DO_GRAFO = exports.AMOSTRA_SCHEMA = exports.STATUS_SCHEMA = void 0;
36
+ exports.CORRECAO_DO_INDICE = exports.USO_DO_GRAFO = exports.AMOSTRA_SCHEMA = exports.STATUS_SCHEMA = void 0;
37
37
  exports.lerPedido = lerPedido;
38
38
  exports.executarGrafo = executarGrafo;
39
39
  /**
@@ -46,6 +46,10 @@ exports.executarGrafo = executarGrafo;
46
46
  * O argv chega cru e o parser e estrito: subcomando conhecido, opcao conhecida uma vez so, valor
47
47
  * onde a opcao pede valor e bandeira sem valor, numero exato de posicionais. Resposta sai 0, mesmo
48
48
  * vazia; erro tipado sai 1 e, com `--json`, vem como objeto.
49
+ *
50
+ * RM-031 KG5 (D4, D5): `--teto-bytes N` limita a resposta em bytes (JSON compacto), para quem a leva
51
+ * ao contexto de um agente, como as tools `ork_grafo_*` do MCP; sem a opcao, a saida e a de antes. Sem
52
+ * o indice do HEAD, a recusa diz se ha indice de outra revisao ou de outro extrator, e a correcao.
49
53
  */
50
54
  const node_crypto_1 = require("node:crypto");
51
55
  const fs = __importStar(require("node:fs"));
@@ -60,20 +64,29 @@ exports.AMOSTRA_SCHEMA = 'ork.graph-edge-audit-sample/v0';
60
64
  exports.USO_DO_GRAFO = [
61
65
  'uso: ork grafo indexar [--verificar] [--forcar] [--json]',
62
66
  ' ork grafo status [--json]',
63
- ' ork grafo vizinhos <no> [--profundidade N] [--sentido entrada|saida|ambos] [--tipo T,...] [--limite N] [--json]',
64
- ' ork grafo chamadores <simbolo> [--profundidade N] [--limite N] [--json]',
65
- ' ork grafo importadores <arquivo|simbolo> [--profundidade N] [--limite N] [--json]',
66
- ' ork grafo caminho <de> <para> [--sentido saida|entrada|ambos] [--tipo T,...] [--json]',
67
+ ' ork grafo vizinhos <no> [--profundidade N] [--sentido entrada|saida|ambos] [--tipo T,...] [--limite N] [--json [--teto-bytes N]]',
68
+ ' ork grafo chamadores <simbolo> [--profundidade N] [--limite N] [--json [--teto-bytes N]]',
69
+ ' ork grafo importadores <arquivo|simbolo> [--profundidade N] [--limite N] [--json [--teto-bytes N]]',
70
+ ' ork grafo caminho <de> <para> [--sentido saida|entrada|ambos] [--tipo T,...] [--json [--teto-bytes N]]',
67
71
  ' ork grafo amostra [--por-estrato N] | ork grafo amostra --conferir ARQ [--json]',
68
72
  ' ork grafo limpar [--tudo] [--json]',
69
73
  ].join('\n');
74
+ /** KG5 (D5): a correcao das recusas de indice que a proxima indexacao resolve. */
75
+ exports.CORRECAO_DO_INDICE = 'ork grafo indexar';
76
+ /** KG5 (D5): o estado do indice do HEAD que cada recusa declara no JSON, ao lado da correcao. */
77
+ const ESTADO_DO_INDICE = Object.freeze({
78
+ 'grafo.indice.ausente': 'nao-indexado',
79
+ 'grafo.indice.outra-revisao': 'outra-revisao',
80
+ 'grafo.indice.outro-extrator': 'outro-extrator',
81
+ 'grafo.indice.corrompido': 'corrompido',
82
+ });
70
83
  const SUBCOMANDOS = Object.freeze({
71
84
  indexar: { posicionais: 0, bandeiras: ['verificar', 'forcar', 'json'], valores: [] },
72
85
  status: { posicionais: 0, bandeiras: ['json'], valores: [] },
73
- vizinhos: { posicionais: 1, bandeiras: ['json'], valores: ['profundidade', 'sentido', 'tipo', 'limite'] },
74
- chamadores: { posicionais: 1, bandeiras: ['json'], valores: ['profundidade', 'limite'] },
75
- importadores: { posicionais: 1, bandeiras: ['json'], valores: ['profundidade', 'limite'] },
76
- caminho: { posicionais: 2, bandeiras: ['json'], valores: ['sentido', 'tipo'] },
86
+ vizinhos: { posicionais: 1, bandeiras: ['json'], valores: ['profundidade', 'sentido', 'tipo', 'limite', 'teto-bytes'] },
87
+ chamadores: { posicionais: 1, bandeiras: ['json'], valores: ['profundidade', 'limite', 'teto-bytes'] },
88
+ importadores: { posicionais: 1, bandeiras: ['json'], valores: ['profundidade', 'limite', 'teto-bytes'] },
89
+ caminho: { posicionais: 2, bandeiras: ['json'], valores: ['sentido', 'tipo', 'teto-bytes'] },
77
90
  amostra: { posicionais: 0, bandeiras: ['json'], valores: ['por-estrato', 'conferir'] },
78
91
  limpar: { posicionais: 0, bandeiras: ['tudo', 'json'], valores: [] },
79
92
  });
@@ -141,8 +154,40 @@ function sentido(p) {
141
154
  uso(`--sentido desconhecido: ${v}`);
142
155
  return v;
143
156
  }
157
+ /** KG5 (D4): o teto vale para o JSON, que e o que vai ao contexto de quem consulta. */
158
+ function tetoDeBytes(p) {
159
+ const teto = inteiro(p, 'teto-bytes');
160
+ if (teto === undefined)
161
+ return undefined;
162
+ if (!p.bandeiras.has('json'))
163
+ uso('--teto-bytes exige --json');
164
+ if (teto < 1)
165
+ uso('--teto-bytes precisa ser ao menos 1');
166
+ return teto;
167
+ }
144
168
  const mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
145
169
  const contagem = (o) => Object.entries(o).map(([k, v]) => `${k}=${v}`).join(' ');
170
+ /** Ate cinco caminhos e quantos ficaram de fora. */
171
+ const alguns = (lista) => (lista.length ? ` (${lista.slice(0, 5).join(', ')}${lista.length > 5 ? ` e mais ${lista.length - 5}` : ''})` : '');
172
+ /** KG4: como a extracao rodou, a base e o que veio dela, ou por que foi completa. */
173
+ function linhasDaExtracao(r) {
174
+ if (r.modo === null)
175
+ return [];
176
+ const a = r.reaproveitamento;
177
+ if (r.modo === 'incremental' && r.base && a) {
178
+ // Desestruturado: o lint de horario le o campo de nome curto do TypeScript como instante.
179
+ const { ts: doTs, md: doMd, mudanca } = a;
180
+ const ts = doTs.modo === 'inteiro' ? `TypeScript inteiro (${doTs.motivo})`
181
+ : doTs.modo === 'parcial' ? `TypeScript parcial, ${doTs.reextraidos.length} reextraido(s)${alguns(doTs.reextraidos)} num programa de ${doTs.programa}, ${doTs.reaproveitados} da base`
182
+ : `TypeScript todo da base (${doTs.reaproveitados})`;
183
+ return [
184
+ ` extracao incremental a partir do indice de ${r.base.revision.slice(0, 12)}: ${mudanca.alterados} alterado(s), ${mudanca.novos} novo(s), ${mudanca.removidos} removido(s)`,
185
+ ` ${ts}`,
186
+ ` Markdown: ${doMd.reextraidos.length} reextraido(s)${alguns(doMd.reextraidos)}, ${doMd.reaproveitados} da base`,
187
+ ];
188
+ }
189
+ return [` extracao completa${r.motivo_completo ? `: ${r.motivo_completo}` : ''}`];
190
+ }
146
191
  function indexar(ctx, p) {
147
192
  const r = (0, intelligence_graph_index_1.construirIndice)(ctx, { verificar: p.bandeiras.has('verificar'), forcar: p.bandeiras.has('forcar') });
148
193
  if (p.bandeiras.has('json')) {
@@ -159,12 +204,18 @@ function indexar(ctx, p) {
159
204
  ` conferencia conferirFontes verificada: ${m.conferencia.fontes} fontes e ${m.conferencia.evidencias} evidencias contra os bytes`,
160
205
  ` nos ${contagem(m.contagens.nos)}`,
161
206
  ` arestas ${contagem(m.contagens.arestas)}`,
162
- ` lugar ${r.dir} (${mb(m.graph_bytes + m.report_bytes)})`,
207
+ ` lugar ${r.dir} (${mb(m.graph_bytes + m.report_bytes + m.units_bytes)})`,
208
+ ...linhasDaExtracao(r),
163
209
  ];
164
210
  if (r.motivo)
165
211
  linhas.push(` motivo ${r.motivo}`);
166
- for (const d of r.determinismo ?? [])
167
- linhas.push(` determinismo ${d.ordem}: digest ${d.digest.slice(0, 16)}, relatorio ${d.relatorio.slice(0, 16)}, ${d.igual ? 'igual' : 'DIFERENTE'}`);
212
+ for (const d of r.determinismo ?? []) {
213
+ linhas.push(` determinismo ${d.ordem}: digest ${d.digest.slice(0, 16)}, relatorio ${d.relatorio.slice(0, 16)}, unidades ${d.unidades.slice(0, 16)}, ${d.igual ? 'igual' : 'DIFERENTE'}`);
214
+ }
215
+ if (r.incremental)
216
+ linhas.push(` incremental a partir do indice de ${r.incremental.base.slice(0, 12)}: ${r.incremental.igual ? 'igual a completa nos quatro arquivos' : 'DIFERENTE'}`);
217
+ else if (r.determinismo)
218
+ linhas.push(` incremental nao conferido: ${r.motivo_completo ?? 'sem base'}`);
168
219
  linhas.push(` tempo ${r.ms} ms`);
169
220
  if (r.determinismo)
170
221
  linhas.push('aprovado');
@@ -211,9 +262,65 @@ function status(ctx, p) {
211
262
  ctx.escrever(linhas.join('\n'));
212
263
  return 0;
213
264
  }
265
+ /**
266
+ * KG5 (CHECK rodada 1, A1): o que difere entre o extrator do indice guardado e o de quem consulta. A
267
+ * versao do Node entra no rotulo do analisador de JavaScript, entao outra instalacao ou outro Node dao
268
+ * outra chave com o mesmo conteudo.
269
+ */
270
+ function diferencasDoExtrator(guardado, atual) {
271
+ const r = [];
272
+ for (const k of Object.keys(atual.analisadores).sort(intelligence_graph_contract_1.compararUtf8)) {
273
+ const antes = guardado.analisadores?.[k];
274
+ if (antes !== atual.analisadores[k])
275
+ r.push(`${k} ${antes ?? '?'} no indice, ${atual.analisadores[k]} aqui`);
276
+ }
277
+ if ((0, intelligence_graph_contract_1.canonico)(guardado.pacotes) !== (0, intelligence_graph_contract_1.canonico)(atual.pacotes))
278
+ r.push('pacotes dos analisadores');
279
+ if (guardado.codigo !== atual.codigo)
280
+ r.push('codigo do extrator');
281
+ return r;
282
+ }
283
+ /**
284
+ * KG5 (D5): sem o indice do HEAD, diz o que ha no lugar. Indice guardado do mesmo repositorio e da
285
+ * revisao do HEAD com outra chave e de outro extrator (instalacao, Node, analisadores ou codigo do
286
+ * extrator mudaram), e a recusa diz o que mudou; de outra revisao, e indice velho ou de outra arvore.
287
+ * Nos dois casos a correcao e indexar de novo, com a instalacao de quem consulta; sem nenhum, fica a
288
+ * recusa original (nao indexado). Nunca responde por um indice que nao e o do HEAD.
289
+ */
290
+ function semIndiceDoHead(ctx, original) {
291
+ const arvore = (0, intelligence_graph_repo_1.revisaoDaArvore)(ctx.raiz);
292
+ if (!arvore.head)
293
+ throw original;
294
+ const perfil = (0, intelligence_graph_index_1.perfilDoIndice)(arvore.raiz, undefined, ctx.repositorio ? { repository_id: ctx.repositorio } : {});
295
+ const chave = (0, intelligence_graph_index_1.chaveDoIndice)(arvore.head, perfil), head = arvore.head.slice(0, 12);
296
+ const guardados = (0, intelligence_graph_index_1.estadoDosIndices)(ctx).indices
297
+ .filter((i) => i.problema === null && i.chave !== chave && i.repository_id === perfil.repository_id && i.revision !== null);
298
+ const doHead = guardados.find((i) => i.revision === arvore.head);
299
+ if (doHead) {
300
+ const diferencas = diferencasDoExtrator((0, intelligence_graph_index_1.lerIndice)(ctx, doHead.chave, { grafo: false }).manifesto, perfil);
301
+ throw new Error(`grafo.indice.outro-extrator: ha indice do HEAD ${head} de outro extrator (${diferencas.join('; ') || 'perfil diferente'}); `
302
+ + `rode ${exports.CORRECAO_DO_INDICE} com a mesma instalacao do ork e o mesmo Node de quem consulta (o servidor MCP consulta com a dele)`);
303
+ }
304
+ if (guardados.length) {
305
+ const revisoes = [...new Set(guardados.map((i) => i.revision.slice(0, 12)))].sort(intelligence_graph_contract_1.compararUtf8);
306
+ // CHECK rodada 2 (N7): a ressalva da instalacao ja na primeira recusa, a de cada commit do GO.
307
+ throw new Error(`grafo.indice.outra-revisao: o HEAD ${head} nao tem indice; o guardado e de outra revisao${alguns(revisoes)}; `
308
+ + `rode ${exports.CORRECAO_DO_INDICE} com a mesma instalacao do ork e o mesmo Node de quem consulta`);
309
+ }
310
+ throw original;
311
+ }
214
312
  /** O indice do HEAD pronto para consulta, com a concessao local e o cabecalho; `grafo` ja vem filtrado por ela. */
215
313
  function consultavel(ctx) {
216
- const { arvore, perfil, chave, indice } = (0, intelligence_graph_index_1.indiceDoHead)(ctx);
314
+ let doHead;
315
+ try {
316
+ doHead = (0, intelligence_graph_index_1.indiceDoHead)(ctx);
317
+ }
318
+ catch (e) {
319
+ if (String(e.message).startsWith('grafo.indice.ausente'))
320
+ semIndiceDoHead(ctx, e);
321
+ throw e;
322
+ }
323
+ const { arvore, perfil, chave, indice } = doHead;
217
324
  const concessao = (0, intelligence_graph_index_1.concessaoLocal)(arvore.raiz, perfil);
218
325
  const grafo = (0, intelligence_graph_query_1.filtrarGrafo)(indice.grafo, concessao), m = indice.manifesto;
219
326
  const cabecalho = {
@@ -227,18 +334,59 @@ function responder(ctx, p, r) {
227
334
  ctx.escrever(p.bandeiras.has('json') ? (0, intelligence_graph_query_1.jsonDaResposta)(r) : (0, intelligence_graph_query_1.textoDaResposta)(r));
228
335
  return 0;
229
336
  }
337
+ /**
338
+ * KG5 (D4): a resposta em JSON compacto que cabe em `teto` bytes. Se a pedida nao cabe, vale o maior
339
+ * limite que cabe: as arestas mais longe do alvo saem primeiro, pela mesma ordem do `--limite`, e
340
+ * `consulta.limite` passa a ser o efetivo. O tamanho cresce com o limite (cada aresta a mais so soma
341
+ * bytes), entao a busca binaria acha o maior que cabe, sempre o mesmo. Aresta nunca sai sem toda a
342
+ * evidencia; o caminho nao se corta.
343
+ */
344
+ function respostaComTeto(perguntar, limite, teto) {
345
+ const pedida = perguntar(limite);
346
+ const limitePedido = pedida.consulta.limite;
347
+ const comTeto = (r, cortado) => JSON.stringify({ ...r, teto: { bytes: teto, limite_pedido: limitePedido, cortado } });
348
+ const inteira = comTeto(pedida, false);
349
+ if (Buffer.byteLength(inteira) <= teto)
350
+ return inteira;
351
+ const ehCaminho = pedida.consulta.tipo === 'caminho';
352
+ // So chega aqui o que nao cabe nem com a aresta mais perto do alvo: estreitar a consulta nao ajuda.
353
+ const excedido = (resposta, bytes) => {
354
+ throw new intelligence_graph_query_1.ErroDeConsulta('grafo.consulta.teto-excedido', `${resposta} tem ${bytes} bytes, acima do teto de ${teto}; ${ehCaminho ? 'o caminho nao se corta: ' : ''}use --teto-bytes maior`);
355
+ };
356
+ if (ehCaminho)
357
+ excedido(`o caminho de ${pedida.arestas.length} passo(s)`, Buffer.byteLength(inteira));
358
+ if (pedida.arestas.length <= 1)
359
+ excedido(`a resposta com ${pedida.arestas.length} aresta(s)`, Buffer.byteLength(inteira));
360
+ let menor = 1, maior = pedida.arestas.length - 1, melhor = null;
361
+ while (menor <= maior) {
362
+ const k = Math.floor((menor + maior) / 2), texto = comTeto(perguntar(k), true);
363
+ if (Buffer.byteLength(texto) <= teto) {
364
+ melhor = texto;
365
+ menor = k + 1;
366
+ }
367
+ else
368
+ maior = k - 1;
369
+ }
370
+ return melhor ?? excedido('a resposta com 1 aresta', Buffer.byteLength(comTeto(perguntar(1), true)));
371
+ }
230
372
  function consultar(ctx, p) {
231
373
  // As opcoes sao conferidas antes de carregar o indice: erro de uso nao custa a leitura do grafo.
232
374
  const [a, b] = p.posicionais;
233
- const profundidade = inteiro(p, 'profundidade'), limite = inteiro(p, 'limite'), s = sentido(p), t = tipos(p);
375
+ const profundidade = inteiro(p, 'profundidade'), limite = inteiro(p, 'limite'), s = sentido(p), t = tipos(p), teto = tetoDeBytes(p);
234
376
  const { g, cabecalho } = consultavel(ctx);
235
- if (p.sub === 'vizinhos')
236
- return responder(ctx, p, (0, intelligence_graph_query_1.vizinhos)(g, cabecalho, a, { profundidade, limite, sentido: s, tipos: t }));
237
- if (p.sub === 'chamadores')
238
- return responder(ctx, p, (0, intelligence_graph_query_1.chamadores)(g, cabecalho, a, { profundidade, limite }));
239
- if (p.sub === 'importadores')
240
- return responder(ctx, p, (0, intelligence_graph_query_1.importadores)(g, cabecalho, a, { profundidade, limite }));
241
- return responder(ctx, p, (0, intelligence_graph_query_1.caminho)(g, cabecalho, a, b, { sentido: s, tipos: t }));
377
+ const perguntar = (lim) => {
378
+ if (p.sub === 'vizinhos')
379
+ return (0, intelligence_graph_query_1.vizinhos)(g, cabecalho, a, { profundidade, limite: lim, sentido: s, tipos: t });
380
+ if (p.sub === 'chamadores')
381
+ return (0, intelligence_graph_query_1.chamadores)(g, cabecalho, a, { profundidade, limite: lim });
382
+ if (p.sub === 'importadores')
383
+ return (0, intelligence_graph_query_1.importadores)(g, cabecalho, a, { profundidade, limite: lim });
384
+ return (0, intelligence_graph_query_1.caminho)(g, cabecalho, a, b, { sentido: s, tipos: t });
385
+ };
386
+ if (teto === undefined)
387
+ return responder(ctx, p, perguntar(limite));
388
+ ctx.escrever(respostaComTeto(perguntar, limite, teto));
389
+ return 0;
242
390
  }
243
391
  const sha256 = (b) => (0, node_crypto_1.createHash)('sha256').update(b).digest('hex');
244
392
  /**
@@ -392,15 +540,21 @@ function limpar(ctx, p) {
392
540
  ctx.escrever([`grafo limpar: ${r.removidos.length} removido(s), ${mb(r.bytes)}`, ...r.removidos.map((x) => ` ${x.nome} ${mb(x.bytes)}`)].join('\n'));
393
541
  return 0;
394
542
  }
395
- const erroEmJson = (ctx, e) => {
543
+ /** KG5 (D4, D5): com `--teto-bytes` o erro tambem sai compacto; recusa de indice traz o estado e a correcao. */
544
+ const erroEmJson = (ctx, e, compacto) => {
396
545
  const [codigo, ...detalhe] = String(e.message).split('\n')[0].split(': ');
397
- ctx.escrever(JSON.stringify({
398
- schema: intelligence_graph_query_1.CONSULTA_SCHEMA, erro: { codigo, detalhe: detalhe.join(': ') || null, candidatos: e instanceof intelligence_graph_query_1.ErroDeConsulta ? e.candidatos : [] },
399
- }, null, 2));
546
+ const doIndice = Object.prototype.hasOwnProperty.call(ESTADO_DO_INDICE, codigo)
547
+ ? { estado_do_indice: ESTADO_DO_INDICE[codigo], correcao: exports.CORRECAO_DO_INDICE } : {};
548
+ const r = {
549
+ schema: intelligence_graph_query_1.CONSULTA_SCHEMA,
550
+ erro: { codigo, detalhe: detalhe.join(': ') || null, candidatos: e instanceof intelligence_graph_query_1.ErroDeConsulta ? e.candidatos : [], ...doIndice },
551
+ };
552
+ ctx.escrever(compacto ? JSON.stringify(r) : JSON.stringify(r, null, 2));
400
553
  return 1;
401
554
  };
402
555
  /** `ork grafo <subcomando> ...`: devolve o codigo de saida; sem `--json`, o erro de uso ou tipado e lancado. */
403
556
  function executarGrafo(argv, ctx) {
557
+ const compacto = argv.some((a) => a === '--teto-bytes' || a.startsWith('--teto-bytes='));
404
558
  let p;
405
559
  try {
406
560
  p = lerPedido(argv);
@@ -408,7 +562,7 @@ function executarGrafo(argv, ctx) {
408
562
  catch (e) {
409
563
  // Com `--json` no argv, tambem o erro de uso sai como objeto, como os demais.
410
564
  if (argv.includes('--json'))
411
- return erroEmJson(ctx, e);
565
+ return erroEmJson(ctx, e, compacto);
412
566
  throw e;
413
567
  }
414
568
  try {
@@ -425,6 +579,6 @@ function executarGrafo(argv, ctx) {
425
579
  catch (e) {
426
580
  if (!p.bandeiras.has('json'))
427
581
  throw e;
428
- return erroEmJson(ctx, e);
582
+ return erroEmJson(ctx, e, compacto);
429
583
  }
430
584
  }
@@ -10,6 +10,7 @@ exports.derivarIds = derivarIds;
10
10
  exports.caminhoValido = caminhoValido;
11
11
  exports.validarGrafo = validarGrafo;
12
12
  exports.conferirFontes = conferirFontes;
13
+ exports.conferirFontesDoGrafoValidado = conferirFontesDoGrafoValidado;
13
14
  /**
14
15
  * I-31 KG1 (D1 a D5, D9, D10): contrato `ork.code-artifact-graph/v1` do grafo deterministico
15
16
  * de codigo e artefatos.
@@ -134,19 +135,70 @@ function compararUtf8(a, b) {
134
135
  }
135
136
  return a.length - b.length;
136
137
  }
137
- /** D2: JSON canonico. Chaves na ordem de bytes UTF-8, sem espaco, so numero finito. */
138
+ const FORMAS = new Map();
139
+ const TETO_DE_FORMAS = 4096;
140
+ let formasGuardadas = 0;
141
+ function formaDe(chaves) {
142
+ const primeira = chaves.length ? chaves[0] : '', candidatas = FORMAS.get(primeira);
143
+ if (candidatas) {
144
+ for (const f of candidatas) {
145
+ if (f.chaves.length !== chaves.length)
146
+ continue;
147
+ let igual = true;
148
+ for (let i = 1; i < chaves.length && igual; i++)
149
+ igual = f.chaves[i] === chaves[i];
150
+ if (igual)
151
+ return f.ordem;
152
+ }
153
+ }
154
+ const ordem = [...chaves].sort(compararUtf8).map((k) => [k, `${JSON.stringify(k)}:`]);
155
+ if (formasGuardadas < TETO_DE_FORMAS) {
156
+ formasGuardadas++;
157
+ if (candidatas)
158
+ candidatas.push({ chaves: [...chaves], ordem });
159
+ else
160
+ FORMAS.set(primeira, [{ chaves: [...chaves], ordem }]);
161
+ }
162
+ return ordem;
163
+ }
164
+ /**
165
+ * D2: JSON canonico. Chaves na ordem de bytes UTF-8, sem espaco, so numero finito. KG4 (D6): mesma
166
+ * saida da forma original (`map` e `join`, com o buraco de array esparso vazio), sem ordenar as
167
+ * chaves a cada objeto; a ordem de visita, e com ela o primeiro erro, e a mesma.
168
+ */
138
169
  function canonico(v) {
139
- if (v === null || typeof v === 'boolean' || typeof v === 'string')
140
- return JSON.stringify(v);
141
- if (typeof v === 'number')
142
- return Number.isFinite(v) ? JSON.stringify(v) : falha('grafo.canonico.numero-invalido');
143
- if (Array.isArray(v))
144
- return `[${v.map(canonico).join(',')}]`;
145
- if (typeof v === 'object') {
146
- const o = v;
147
- return `{${Object.keys(o).sort(compararUtf8).map((k) => `${JSON.stringify(k)}:${canonico(o[k])}`).join(',')}}`;
170
+ if (v === null)
171
+ return 'null';
172
+ switch (typeof v) {
173
+ case 'string':
174
+ return JSON.stringify(v);
175
+ case 'boolean':
176
+ return v ? 'true' : 'false';
177
+ case 'number':
178
+ return Number.isFinite(v) ? JSON.stringify(v) : falha('grafo.canonico.numero-invalido');
179
+ case 'object': {
180
+ if (Array.isArray(v)) {
181
+ let s = '[';
182
+ for (let i = 0; i < v.length; i++) {
183
+ if (i > 0)
184
+ s += ',';
185
+ if (i in v)
186
+ s += canonico(v[i]);
187
+ }
188
+ return `${s}]`;
189
+ }
190
+ const o = v, forma = formaDe(Object.keys(o));
191
+ let s = '{';
192
+ for (let i = 0; i < forma.length; i++) {
193
+ if (i > 0)
194
+ s += ',';
195
+ s += forma[i][1] + canonico(o[forma[i][0]]);
196
+ }
197
+ return `${s}}`;
198
+ }
199
+ default:
200
+ return falha('grafo.canonico.tipo-invalido');
148
201
  }
149
- return falha('grafo.canonico.tipo-invalido');
150
202
  }
151
203
  /** SHA-256 sobre os bytes UTF-8 do conteudo canonico. */
152
204
  const sha256DoCanonico = (v) => (0, node_crypto_1.createHash)('sha256').update(canonico(v), 'utf8').digest('hex');
@@ -508,7 +560,14 @@ function linhaDoByte(quebras, o) {
508
560
  * parser interpretou a relacao certo; essa prova vem da auditoria de arestas do benchmark.
509
561
  */
510
562
  function conferirFontes(entrada, fontes) {
511
- const g = validarGrafo(entrada);
563
+ return conferirFontesDoGrafoValidado(validarGrafo(entrada), fontes);
564
+ }
565
+ /**
566
+ * RM-031 KG4 (D6): a conferencia de `conferirFontes` sobre o grafo que `validarGrafo` acabou de
567
+ * devolver, sem validar de novo. So para o produtor que tem esse retorno em maos (a construcao do
568
+ * indice, logo depois da extracao); qualquer outra entrada passa por `conferirFontes`.
569
+ */
570
+ function conferirFontesDoGrafoValidado(g, fontes) {
512
571
  const manifesto = new Map(g.snapshot.source_manifest.map((m) => [m.path, m]));
513
572
  for (const [p, f] of fontes) {
514
573
  if (!manifesto.has(p))
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TETO_DE_LINHAS_DE_TABELA = exports.TOKENS_SEM_MENCAO = exports.CHAVES_DO_FRONTMATTER = exports.PADRAO_DE_ID = void 0;
4
4
  exports.slugDeTexto = slugDeTexto;
5
+ exports.estruturarMarkdown = estruturarMarkdown;
5
6
  exports.extrairMarkdown = extrairMarkdown;
7
+ exports.ligarMarkdown = ligarMarkdown;
6
8
  const yaml_1 = require("./yaml");
7
9
  /** D6: ID de artefato governado (`RM-031`, `FEAT-018`). */
8
10
  exports.PADRAO_DE_ID = /^[A-Z][A-Z0-9]*-[0-9]+$/;
@@ -329,7 +331,25 @@ const decodificar = (t) => {
329
331
  return null;
330
332
  }
331
333
  };
334
+ function estruturarMarkdown(fonte, e) {
335
+ const s = estruturar(fonte, e.aceitaFragmento, e.analisar, e.referencia);
336
+ const mencoes = [];
337
+ for (const l of s.textoDeMencao)
338
+ for (const m of l.texto.matchAll(MENCAO))
339
+ mencoes.push({ inicio: l.inicio + m.index, id: m[0] });
340
+ const d = s.frontmatter?.dados;
341
+ return {
342
+ path: fonte.path, secoes: s.secoes, links: s.links, mencoes,
343
+ frontmatter: s.frontmatter && d
344
+ ? { id: typeof d.id === 'string' ? d.id : null, tipo: typeof d.tipo === 'string' && d.tipo !== '', valores: s.frontmatter.valores }
345
+ : null,
346
+ frontmatterInvalido: s.frontmatterInvalido, tabelaGrande: s.tabelaGrande,
347
+ };
348
+ }
332
349
  function extrairMarkdown(e) {
350
+ return ligarMarkdown({ ...e, estruturas: e.fontes.map((f) => estruturarMarkdown(f, e)) });
351
+ }
352
+ function ligarMarkdown(e) {
333
353
  const saida = { nos: [], arestas: [], diagnosticos: [], lacunas: [] };
334
354
  const arquivos = new Set(e.arquivos), diretorios = new Set();
335
355
  for (const p of e.arquivos) {
@@ -337,8 +357,8 @@ function extrairMarkdown(e) {
337
357
  for (let i = 1; i < partes.length; i++)
338
358
  diretorios.add(partes.slice(0, i).join('/'));
339
359
  }
340
- const estruturas = e.fontes.map((f) => estruturar(f, e.aceitaFragmento, e.analisar, e.referencia));
341
- const slugs = new Map(estruturas.map((s) => [s.fonte.path, new Set(s.secoes.map((x) => x.slug).filter((x) => x !== null))]));
360
+ const estruturas = e.estruturas;
361
+ const slugs = new Map(estruturas.map((s) => [s.path, new Set(s.secoes.map((x) => x.slug).filter((x) => x !== null))]));
342
362
  const aresta = (kind, from, to, extrator, metodo, t) => {
343
363
  saida.arestas.push({ kind, from, to, extrator, metodo, trecho: t });
344
364
  };
@@ -349,17 +369,17 @@ function extrairMarkdown(e) {
349
369
  const donos = new Map();
350
370
  for (const s of estruturas) {
351
371
  if (s.frontmatterInvalido)
352
- lacuna('frontmatter-invalido', s.fonte.path, 0, null);
372
+ lacuna('frontmatter-invalido', s.path, 0, null);
353
373
  if (s.tabelaGrande)
354
- lacuna('markdown-tabela-grande', s.fonte.path, null, null);
355
- const nome = s.fonte.path.slice(s.fonte.path.lastIndexOf('/') + 1), d = s.frontmatter?.dados;
356
- if (!d || nome.startsWith('_') || typeof d.id !== 'string' || !exports.PADRAO_DE_ID.test(d.id) || typeof d.tipo !== 'string' || !d.tipo)
374
+ lacuna('markdown-tabela-grande', s.path, null, null);
375
+ const nome = s.path.slice(s.path.lastIndexOf('/') + 1), d = s.frontmatter;
376
+ if (!d || nome.startsWith('_') || d.id === null || !exports.PADRAO_DE_ID.test(d.id) || !d.tipo)
357
377
  continue;
358
378
  if (!e.aceitaFragmento(d.id)) {
359
- lacuna('artefato-recusado', s.fonte.path, 0, null);
379
+ lacuna('artefato-recusado', s.path, 0, null);
360
380
  continue;
361
381
  }
362
- donos.set(d.id, [...(donos.get(d.id) ?? []), s.fonte.path]);
382
+ donos.set(d.id, [...(donos.get(d.id) ?? []), s.path]);
363
383
  }
364
384
  const artefatos = new Map();
365
385
  for (const [id, caminhos] of donos) {
@@ -385,20 +405,15 @@ function extrairMarkdown(e) {
385
405
  const atual = baixo > 0 ? s.secoes[baixo - 1] : null;
386
406
  return atual && atual.slug !== null ? { kind: 'section', path: arquivo.path, fragment: atual.slug } : arquivo;
387
407
  };
388
- const mencoes = (fonte, linhas, origem) => {
389
- for (const l of linhas) {
390
- for (const m of l.texto.matchAll(MENCAO)) {
391
- const alvo = artefatos.get(m[0]), inicio = l.inicio + m.index;
392
- if (!alvo) {
393
- lacuna('id-sem-artefato', fonte.path, inicio, m[0]);
394
- continue;
395
- }
396
- aresta('references', origem(inicio), alvo, e.extratorId, 'text-location', { path: fonte.path, inicio, fim: inicio + m[0].length });
397
- }
398
- }
408
+ const mencao = (path, inicio, id, origem) => {
409
+ const alvo = artefatos.get(id);
410
+ if (!alvo)
411
+ lacuna('id-sem-artefato', path, inicio, id);
412
+ else
413
+ aresta('references', origem(inicio), alvo, e.extratorId, 'text-location', { path, inicio, fim: inicio + id.length });
399
414
  };
400
415
  for (const s of estruturas) {
401
- const path = s.fonte.path, arquivo = { kind: 'file', path, fragment: null };
416
+ const path = s.path, arquivo = { kind: 'file', path, fragment: null };
402
417
  for (const x of s.secoes) {
403
418
  if (x.slug === null) {
404
419
  lacuna('secao-recusada', path, x.inicio, null);
@@ -444,7 +459,7 @@ function extrairMarkdown(e) {
444
459
  continue;
445
460
  aresta('references', origem, { kind: 'file', path: alvo, fragment: null }, e.extratorMd, 'explicit-link', t);
446
461
  }
447
- const artefato = s.frontmatter && typeof s.frontmatter.dados.id === 'string' ? artefatos.get(s.frontmatter.dados.id) : undefined;
462
+ const artefato = s.frontmatter && s.frontmatter.id !== null ? artefatos.get(s.frontmatter.id) : undefined;
448
463
  if (artefato && artefato.path === path && s.frontmatter) {
449
464
  for (const v of s.frontmatter.valores) {
450
465
  const [kind, destino] = exports.CHAVES_DO_FRONTMATTER[v.chave].split(':');
@@ -459,11 +474,14 @@ function extrairMarkdown(e) {
459
474
  lacuna('frontmatter-sem-alvo', path, v.inicio, `${v.chave}: ${v.valor}`);
460
475
  }
461
476
  }
462
- mencoes(s.fonte, s.textoDeMencao, (o) => secaoEm(s, arquivo, o));
477
+ for (const m of s.mencoes)
478
+ mencao(path, m.inicio, m.id, (o) => secaoEm(s, arquivo, o));
463
479
  }
464
480
  for (const f of e.codigo) {
465
481
  const arquivo = { kind: 'file', path: f.path, fragment: null };
466
- mencoes(f, linhasDe(f.texto), () => arquivo);
482
+ for (const l of linhasDe(f.texto))
483
+ for (const m of l.texto.matchAll(MENCAO))
484
+ mencao(f.path, l.inicio + m.index, m[0], () => arquivo);
467
485
  }
468
486
  return saida;
469
487
  }