@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
@@ -0,0 +1,469 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TETO_DE_LINHAS_DE_TABELA = exports.TOKENS_SEM_MENCAO = exports.CHAVES_DO_FRONTMATTER = exports.PADRAO_DE_ID = void 0;
4
+ exports.slugDeTexto = slugDeTexto;
5
+ exports.extrairMarkdown = extrairMarkdown;
6
+ const yaml_1 = require("./yaml");
7
+ /** D6: ID de artefato governado (`RM-031`, `FEAT-018`). */
8
+ exports.PADRAO_DE_ID = /^[A-Z][A-Z0-9]*-[0-9]+$/;
9
+ const MENCAO = /\b[A-Z][A-Z0-9]*-[0-9]+\b/g;
10
+ /** D6: chaves do frontmatter que viram aresta, com o tipo e o destino. */
11
+ exports.CHAVES_DO_FRONTMATTER = Object.freeze({
12
+ pai: 'references:artifact', roadmap: 'references:artifact', features: 'references:artifact',
13
+ 'fontes.codigo': 'derived_from:file', 'fontes.testes': 'derived_from:file', 'fontes.docs': 'derived_from:file',
14
+ 'fontes.simbolos': 'references:symbol',
15
+ });
16
+ /** Tokens cujo texto nao e prosa: nao geram mencao de ID (o span de codigo, `codeText`, gera). */
17
+ exports.TOKENS_SEM_MENCAO = Object.freeze([
18
+ 'autolink', 'codeFenced', 'codeIndented', 'definition', 'htmlFlow', 'htmlText', 'resourceDestinationString', 'resourceTitleString',
19
+ ]);
20
+ /** Titulo maior que isso daria slug acima do teto de fragmento. */
21
+ const TETO_DO_TITULO = 2048;
22
+ /** Linha de frontmatter maior que isso nao vai ao leitor de YAML, que e quadratico em linha longa. */
23
+ const TETO_DA_LINHA_DE_FRONTMATTER = 4096;
24
+ /**
25
+ * A tabela GFM do micromark e quadratica nas linhas: acima disso, somando as linhas dos blocos que tem
26
+ * uma linha delimitadora de tabela (`| --- |`, `--- | ---`), o corpo nao e analisado.
27
+ */
28
+ exports.TETO_DE_LINHAS_DE_TABELA = 2000;
29
+ const DELIMITADOR_DE_TABELA = /^[\s>]*[|:\- \t]+$/;
30
+ /** Tokens do titulo cujo texto entra no slug, como o GitHub o renderiza. */
31
+ const TEXTO_DO_TITULO = new Set(['data', 'codeTextData', 'characterEscapeValue', 'autolinkProtocol', 'autolinkEmail']);
32
+ /** Tokens do titulo que nao aparecem no texto renderizado: destino, rotulo de referencia e imagem. */
33
+ const FORA_DO_TEXTO_DO_TITULO = new Set(['resource', 'reference', 'image']);
34
+ /** Linhas do texto a partir de `desde`, com os offsets no texto inteiro. */
35
+ function linhasDe(texto, desde = 0) {
36
+ const r = [];
37
+ let inicio = desde;
38
+ for (;;) {
39
+ const fim = texto.indexOf('\n', inicio);
40
+ const bruto = texto.slice(inicio, fim < 0 ? texto.length : fim);
41
+ r.push({ inicio, texto: bruto.endsWith('\r') ? bruto.slice(0, -1) : bruto });
42
+ if (fim < 0)
43
+ return r;
44
+ inicio = fim + 1;
45
+ }
46
+ }
47
+ /** Troca os trechos por espacos, mantendo as quebras de linha e os offsets, numa passada so. */
48
+ function apagarTrechos(texto, trechos) {
49
+ if (!trechos.length)
50
+ return texto;
51
+ const ordenados = [...trechos].sort((a, b) => a[0] - b[0]), partes = [];
52
+ let pos = 0;
53
+ for (const [de, ate] of ordenados) {
54
+ const i = Math.max(de, pos);
55
+ if (ate <= i)
56
+ continue;
57
+ partes.push(texto.slice(pos, i), texto.slice(i, ate).replace(/[^\r\n]/g, ' '));
58
+ pos = ate;
59
+ }
60
+ partes.push(texto.slice(pos));
61
+ return partes.join('');
62
+ }
63
+ /** D5: ancora como o GitHub gera do texto renderizado: minusculas, sem pontuacao, espaco vira hifen. */
64
+ function slugDeTexto(texto) {
65
+ return texto.toLowerCase().replace(/[^\p{L}\p{M}\p{N}\p{Pc} -]/gu, '').replace(/ /g, '-');
66
+ }
67
+ /** Slugs repetidos no mesmo arquivo ganham `-1`, `-2`, como no GitHub. */
68
+ function contadorDeSlugs() {
69
+ const vistos = new Map();
70
+ return (base) => {
71
+ if (!vistos.has(base)) {
72
+ vistos.set(base, 0);
73
+ return base;
74
+ }
75
+ let n = vistos.get(base), s;
76
+ do
77
+ s = `${base}-${++n}`;
78
+ while (vistos.has(s));
79
+ vistos.set(base, n);
80
+ vistos.set(s, 0);
81
+ return s;
82
+ };
83
+ }
84
+ /** Posicao de cada valor das chaves do D6, lida linha a linha e conferida contra o leitor de YAML. */
85
+ function valoresPosicionados(linhas, dados) {
86
+ const r = [], pilha = [];
87
+ const chaveDe = (indent) => pilha.filter((p) => p.indent < indent).map((p) => p.chave).join('.');
88
+ const valorDe = (caminho) => {
89
+ let v = dados;
90
+ for (const k of caminho.split('.'))
91
+ v = v && typeof v === 'object' && !Array.isArray(v) ? v[k] : undefined;
92
+ return v;
93
+ };
94
+ // Um conjunto por chave: conferir cada item da lista fica linear no tamanho do frontmatter.
95
+ const conjuntos = new Map();
96
+ const aceita = (caminho, valor) => {
97
+ let c = conjuntos.get(caminho);
98
+ if (!c) {
99
+ const v = valorDe(caminho);
100
+ c = new Set(Array.isArray(v) ? v.map(String) : v !== null && v !== undefined ? [String(v)] : []);
101
+ conjuntos.set(caminho, c);
102
+ }
103
+ return c.has(valor);
104
+ };
105
+ const escalar = (caminho, bruto, desde) => {
106
+ if (!(caminho in exports.CHAVES_DO_FRONTMATTER))
107
+ return;
108
+ // Comentario YAML comeca em `#` depois de espaco; busca linear, sem retrocesso em espaco longo.
109
+ const comentario = bruto.search(/\s#/);
110
+ let t = comentario < 0 ? bruto : bruto.slice(0, comentario), ini = desde + (bruto.length - bruto.trimStart().length);
111
+ t = t.trim();
112
+ if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
113
+ t = t.slice(1, -1);
114
+ ini++;
115
+ }
116
+ if (t && t !== 'null' && t !== '~' && aceita(caminho, t))
117
+ r.push({ chave: caminho, valor: t, inicio: ini, fim: ini + t.length });
118
+ };
119
+ for (const l of linhas) {
120
+ if (!l.texto.trim() || l.texto.trimStart().startsWith('#'))
121
+ continue;
122
+ const indent = l.texto.length - l.texto.trimStart().length;
123
+ const item = /^(\s*)-\s+(.*)$/.exec(l.texto);
124
+ if (item) {
125
+ escalar(chaveDe(indent + 1), item[2], l.inicio + l.texto.length - item[2].length);
126
+ continue;
127
+ }
128
+ const m = /^(\s*)([A-Za-z0-9_-]+):(\s*)(.*)$/.exec(l.texto);
129
+ if (!m)
130
+ continue;
131
+ while (pilha.length && pilha[pilha.length - 1].indent >= indent)
132
+ pilha.pop();
133
+ pilha.push({ indent, chave: m[2] });
134
+ const caminho = pilha.map((p) => p.chave).join('.'), resto = m[4], desde = l.inicio + l.texto.length - resto.length;
135
+ if (resto.startsWith('[')) {
136
+ const fecha = resto.indexOf(']');
137
+ if (fecha < 0)
138
+ continue;
139
+ let pos = 1;
140
+ for (const parte of resto.slice(1, fecha).split(',')) {
141
+ escalar(caminho, parte, desde + pos);
142
+ pos += parte.length + 1;
143
+ }
144
+ }
145
+ else if (resto.trim())
146
+ escalar(caminho, resto, desde);
147
+ }
148
+ return r;
149
+ }
150
+ function estruturar(fonte, aceitaFragmento, analisar, referencia) {
151
+ // O BOM nao e conteudo: sem ele, o primeiro titulo e o frontmatter sao lidos.
152
+ const desde = fonte.texto.charCodeAt(0) === 0xfeff ? 1 : 0;
153
+ const todas = linhasDe(fonte.texto, desde);
154
+ let fimDoFrontmatter = desde, frontmatter = null, frontmatterInvalido = false;
155
+ if (todas[0]?.texto === '---') {
156
+ const fim = todas.findIndex((l, i) => i > 0 && (l.texto === '---' || l.texto === '...'));
157
+ if (fim > 0) {
158
+ fimDoFrontmatter = todas[fim].inicio + todas[fim].texto.length;
159
+ const bruto = todas.slice(1, fim);
160
+ try {
161
+ if (bruto.some((l) => l.texto.length > TETO_DA_LINHA_DE_FRONTMATTER))
162
+ throw new Error('frontmatter.linha-longa');
163
+ const dados = (0, yaml_1.lerYaml)(bruto.map((l) => l.texto).join('\n'));
164
+ if (dados && typeof dados === 'object' && !Array.isArray(dados))
165
+ frontmatter = { dados, valores: valoresPosicionados(bruto, dados) };
166
+ }
167
+ catch {
168
+ frontmatterInvalido = true;
169
+ }
170
+ }
171
+ }
172
+ // O analisador ve o corpo sem o BOM (como o GitHub) e com o frontmatter em espacos; os offsets dele
173
+ // contam a partir do fim do BOM, e `desde` os devolve ao texto inteiro.
174
+ const corpo = apagarTrechos(fonte.texto.slice(desde), [[0, fimDoFrontmatter - desde]]);
175
+ const no = (i) => i + desde;
176
+ // A linha do corpo de uma tabela nao precisa de `|`: conta o bloco inteiro que tem delimitador. Linha e
177
+ // linha em branco como o CommonMark as ve: fim de linha LF, CRLF ou CR, e branca so com espaco e tab.
178
+ let linhasDeTabela = 0, bloco = 0, comDelimitador = false;
179
+ for (const l of [...corpo.split(/\r\n|\r|\n/), '']) {
180
+ if (/^[ \t]*$/.test(l)) {
181
+ if (comDelimitador)
182
+ linhasDeTabela += bloco;
183
+ bloco = 0;
184
+ comDelimitador = false;
185
+ continue;
186
+ }
187
+ bloco++;
188
+ if (l.includes('|') && l.includes('-') && DELIMITADOR_DE_TABELA.test(l))
189
+ comDelimitador = true;
190
+ }
191
+ if (linhasDeTabela > exports.TETO_DE_LINHAS_DE_TABELA) {
192
+ return { fonte, secoes: [], links: [], textoDeMencao: [], frontmatter, frontmatterInvalido, tabelaGrande: true };
193
+ }
194
+ const secoes = [], links = [], semMencao = [];
195
+ const slug = contadorDeSlugs(), abertos = [];
196
+ // B5: referencia de caractere pelo decodificador do micromark (as entidades do HTML5); a crua fica.
197
+ const decodificada = (i, f) => referencia(corpo.slice(i + 1, f - 1)) ?? corpo.slice(i, f);
198
+ // Destino do link como o CommonMark o le: dado, escape e referencia, montados dos eventos.
199
+ let destinoAberto = null;
200
+ // Rodadas 7 e 8, B1: so o espaco e o tab crus das pontas saem, antes de decodificar (`<a.md >` vira
201
+ // `a.md`); VT, FF e a referencia que decodifica em espaco (`&Tab;`, `&nbsp;`) ficam, como no GitHub.
202
+ const destinoAparado = (partes) => {
203
+ const r = partes.map(([t, cru]) => [t, cru]);
204
+ while (r.length && r[0][1]) {
205
+ r[0][0] = r[0][0].replace(/^[ \t]+/, '');
206
+ if (r[0][0])
207
+ break;
208
+ r.shift();
209
+ }
210
+ while (r.length && r[r.length - 1][1]) {
211
+ r[r.length - 1][0] = r[r.length - 1][0].replace(/[ \t]+$/, '');
212
+ if (r[r.length - 1][0])
213
+ break;
214
+ r.pop();
215
+ }
216
+ return r.map(([t]) => t).join('');
217
+ };
218
+ // A-N4: o slug sai do texto que o GitHub renderiza no titulo (dado, codigo, escape, entidade e endereco
219
+ // de autolink), sem marcador de enfase, HTML, destino de link, rotulo de referencia nem texto
220
+ // alternativo de imagem. Nada e aparado: o espaco antes de uma imagem no fim vira hifen, como no
221
+ // GitHub (B-R2), e a quebra de linha fora de codigo nao vira espaco (B-P2).
222
+ let titulo = null;
223
+ // Tabela GFM: celula alem das colunas do cabecalho e descartada pelo GitHub, e o link nela tambem.
224
+ let colunas = 0, noCabecalho = false, celula = 0, emExcesso = false;
225
+ for (const e of analisar(corpo)) {
226
+ if (e.entrada) {
227
+ if (e.tipo === 'table')
228
+ colunas = 0;
229
+ else if (e.tipo === 'tableHead')
230
+ noCabecalho = true;
231
+ else if (e.tipo === 'tableRow')
232
+ celula = 0;
233
+ else if (e.tipo === 'tableHeader' && noCabecalho)
234
+ colunas++;
235
+ else if (e.tipo === 'tableData' && ++celula > colunas) {
236
+ emExcesso = true;
237
+ semMencao.push([e.inicio, e.fim]);
238
+ }
239
+ // Titulo, lido em paralelo: o texto dele junta dado, codigo, escape e entidade.
240
+ if (e.tipo === 'atxHeading' || e.tipo === 'setextHeading')
241
+ titulo = { inicio: no(e.inicio), fim: no(e.fim), partes: [], noTexto: 0, foraDoTexto: 0, noCodigo: 0 };
242
+ else if (titulo) {
243
+ if (e.tipo === 'atxHeadingText' || e.tipo === 'setextHeadingText')
244
+ titulo.noTexto++;
245
+ else if (FORA_DO_TEXTO_DO_TITULO.has(e.tipo))
246
+ titulo.foraDoTexto++;
247
+ else if (e.tipo === 'codeText')
248
+ titulo.noCodigo++;
249
+ else if (titulo.noTexto > 0 && titulo.foraDoTexto === 0) {
250
+ if (TEXTO_DO_TITULO.has(e.tipo))
251
+ titulo.partes.push(corpo.slice(e.inicio, e.fim));
252
+ else if (e.tipo === 'characterReference')
253
+ titulo.partes.push(decodificada(e.inicio, e.fim));
254
+ // Em span de codigo a quebra de linha vira espaco (CommonMark); fora dele, o GitHub a descarta.
255
+ else if (e.tipo === 'codeTextLineEnding' || (e.tipo === 'lineEnding' && titulo.noCodigo > 0))
256
+ titulo.partes.push(' ');
257
+ }
258
+ }
259
+ // Link e imagem, tambem dentro de titulo.
260
+ if (e.tipo === 'link' || e.tipo === 'image')
261
+ abertos.push({ inicio: no(e.inicio), fim: no(e.fim), destino: null, descartado: emExcesso });
262
+ else if (e.tipo === 'resourceDestinationString' && abertos.length && !abertos[abertos.length - 1].destino) {
263
+ destinoAberto = abertos[abertos.length - 1].destino = [];
264
+ }
265
+ else if (destinoAberto) {
266
+ if (e.tipo === 'data')
267
+ destinoAberto.push([corpo.slice(e.inicio, e.fim), true]);
268
+ else if (e.tipo === 'characterEscapeValue')
269
+ destinoAberto.push([corpo.slice(e.inicio, e.fim), false]);
270
+ else if (e.tipo === 'characterReference')
271
+ destinoAberto.push([decodificada(e.inicio, e.fim), false]);
272
+ }
273
+ if (exports.TOKENS_SEM_MENCAO.includes(e.tipo))
274
+ semMencao.push([e.inicio, e.fim]);
275
+ continue;
276
+ }
277
+ if (e.tipo === 'tableHead')
278
+ noCabecalho = false;
279
+ else if (e.tipo === 'tableData')
280
+ emExcesso = false;
281
+ else if (titulo && (e.tipo === 'atxHeadingText' || e.tipo === 'setextHeadingText'))
282
+ titulo.noTexto--;
283
+ else if (titulo && FORA_DO_TEXTO_DO_TITULO.has(e.tipo))
284
+ titulo.foraDoTexto--;
285
+ else if (titulo && e.tipo === 'codeText')
286
+ titulo.noCodigo--;
287
+ else if (e.tipo === 'resourceDestinationString')
288
+ destinoAberto = null;
289
+ if ((e.tipo === 'atxHeading' || e.tipo === 'setextHeading') && titulo) {
290
+ // Todo titulo abre uma secao: sem texto ou com slug recusado, o trecho sob ele fica no arquivo,
291
+ // nunca na secao anterior.
292
+ const texto = titulo.partes.join('');
293
+ const base = texto && texto.length <= TETO_DO_TITULO ? slugDeTexto(texto) : '';
294
+ const s = base ? slug(base) : null;
295
+ secoes.push({ slug: s !== null && aceitaFragmento(s) ? s : null, inicio: titulo.inicio, fim: titulo.fim });
296
+ titulo = null;
297
+ }
298
+ else if (e.tipo === 'link' || e.tipo === 'image') {
299
+ const l = abertos.pop();
300
+ if (l?.destino && !l.descartado)
301
+ links.push({ destino: destinoAparado(l.destino), inicio: l.inicio, fim: l.fim });
302
+ }
303
+ }
304
+ const textoDeMencao = linhasDe(apagarTrechos(corpo, semMencao)).map((l) => ({ inicio: no(l.inicio), texto: l.texto }));
305
+ return { fonte, secoes: secoes.sort((a, b) => a.inicio - b.inicio), links, textoDeMencao, frontmatter, frontmatterInvalido, tabelaGrande: false };
306
+ }
307
+ /** Caminho relativo a raiz a partir do arquivo do link; `null` se sair do repositorio. */
308
+ function resolverCaminho(base, destino) {
309
+ const dir = base.includes('/') ? base.slice(0, base.lastIndexOf('/')) : '';
310
+ const partes = (destino.startsWith('/') ? destino.slice(1) : dir ? `${dir}/${destino}` : destino).split('/'), r = [];
311
+ for (const p of partes) {
312
+ if (p === '' || p === '.')
313
+ continue;
314
+ if (p === '..') {
315
+ if (!r.length)
316
+ return null;
317
+ r.pop();
318
+ }
319
+ else
320
+ r.push(p);
321
+ }
322
+ return r.join('/');
323
+ }
324
+ const decodificar = (t) => {
325
+ try {
326
+ return decodeURIComponent(t);
327
+ }
328
+ catch {
329
+ return null;
330
+ }
331
+ };
332
+ function extrairMarkdown(e) {
333
+ const saida = { nos: [], arestas: [], diagnosticos: [], lacunas: [] };
334
+ const arquivos = new Set(e.arquivos), diretorios = new Set();
335
+ for (const p of e.arquivos) {
336
+ const partes = p.split('/');
337
+ for (let i = 1; i < partes.length; i++)
338
+ diretorios.add(partes.slice(0, i).join('/'));
339
+ }
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))]));
342
+ const aresta = (kind, from, to, extrator, metodo, t) => {
343
+ saida.arestas.push({ kind, from, to, extrator, metodo, trecho: t });
344
+ };
345
+ const lacuna = (categoria, path, inicio, detalhe) => {
346
+ saida.lacunas.push({ categoria, path, inicio, detalhe });
347
+ };
348
+ // D6: artefatos; ID repetido em dois arquivos nao prova destino e fica fora dos dois.
349
+ const donos = new Map();
350
+ for (const s of estruturas) {
351
+ if (s.frontmatterInvalido)
352
+ lacuna('frontmatter-invalido', s.fonte.path, 0, null);
353
+ 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)
357
+ continue;
358
+ if (!e.aceitaFragmento(d.id)) {
359
+ lacuna('artefato-recusado', s.fonte.path, 0, null);
360
+ continue;
361
+ }
362
+ donos.set(d.id, [...(donos.get(d.id) ?? []), s.fonte.path]);
363
+ }
364
+ const artefatos = new Map();
365
+ for (const [id, caminhos] of donos) {
366
+ if (caminhos.length === 1)
367
+ artefatos.set(id, { kind: 'artifact', path: caminhos[0], fragment: id });
368
+ else
369
+ for (const p of caminhos)
370
+ lacuna('artefato-id-repetido', p, 0, id);
371
+ }
372
+ for (const r of artefatos.values())
373
+ saida.nos.push(r);
374
+ // Trecho sob titulo recusado fica no arquivo: atribuir a secao anterior seria outra secao.
375
+ const secaoEm = (s, arquivo, o) => {
376
+ // Ultima secao que comeca ate `o`, por busca binaria (as secoes estao em ordem de inicio).
377
+ let baixo = 0, alto = s.secoes.length;
378
+ while (baixo < alto) {
379
+ const meio = (baixo + alto) >> 1;
380
+ if (s.secoes[meio].inicio <= o)
381
+ baixo = meio + 1;
382
+ else
383
+ alto = meio;
384
+ }
385
+ const atual = baixo > 0 ? s.secoes[baixo - 1] : null;
386
+ return atual && atual.slug !== null ? { kind: 'section', path: arquivo.path, fragment: atual.slug } : arquivo;
387
+ };
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
+ }
399
+ };
400
+ for (const s of estruturas) {
401
+ const path = s.fonte.path, arquivo = { kind: 'file', path, fragment: null };
402
+ for (const x of s.secoes) {
403
+ if (x.slug === null) {
404
+ lacuna('secao-recusada', path, x.inicio, null);
405
+ continue;
406
+ }
407
+ const r = { kind: 'section', path, fragment: x.slug };
408
+ saida.nos.push(r);
409
+ aresta('contains', arquivo, r, e.extratorMd, 'structured', { path, inicio: x.inicio, fim: x.fim });
410
+ }
411
+ for (const k of s.links) {
412
+ const origem = secaoEm(s, arquivo, k.inicio), t = { path, inicio: k.inicio, fim: k.fim };
413
+ const destino = k.destino;
414
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(destino)) {
415
+ lacuna('link-externo', path, k.inicio, null);
416
+ continue;
417
+ }
418
+ const cerquilha = destino.indexOf('#');
419
+ const parteDoCaminho = (cerquilha < 0 ? destino : destino.slice(0, cerquilha)).replace(/\?.*$/, '');
420
+ const ancora = cerquilha < 0 ? null : decodificar(destino.slice(cerquilha + 1));
421
+ const caminhoDecodificado = decodificar(parteDoCaminho);
422
+ if (caminhoDecodificado === null) {
423
+ lacuna('link-invalido', path, k.inicio, destino);
424
+ continue;
425
+ }
426
+ const alvo = caminhoDecodificado === '' ? path : resolverCaminho(path, caminhoDecodificado);
427
+ if (alvo === null) {
428
+ lacuna('link-fora-do-repositorio', path, k.inicio, destino);
429
+ continue;
430
+ }
431
+ if (!arquivos.has(alvo)) {
432
+ lacuna(alvo === '' || diretorios.has(alvo) ? 'link-para-diretorio' : 'link-sem-alvo', path, k.inicio, destino);
433
+ continue;
434
+ }
435
+ const secoesDoAlvo = slugs.get(alvo);
436
+ if (ancora && secoesDoAlvo?.has(ancora)) {
437
+ aresta('references', origem, { kind: 'section', path: alvo, fragment: ancora }, e.extratorMd, 'explicit-link', t);
438
+ continue;
439
+ }
440
+ // P6: ancora que nao bate fica declarada; o link ainda prova a referencia ao arquivo.
441
+ if (ancora && secoesDoAlvo)
442
+ lacuna('ancora-nao-resolvida', path, k.inicio, destino);
443
+ if (alvo === path)
444
+ continue;
445
+ aresta('references', origem, { kind: 'file', path: alvo, fragment: null }, e.extratorMd, 'explicit-link', t);
446
+ }
447
+ const artefato = s.frontmatter && typeof s.frontmatter.dados.id === 'string' ? artefatos.get(s.frontmatter.dados.id) : undefined;
448
+ if (artefato && artefato.path === path && s.frontmatter) {
449
+ for (const v of s.frontmatter.valores) {
450
+ const [kind, destino] = exports.CHAVES_DO_FRONTMATTER[v.chave].split(':');
451
+ const t = { path, inicio: v.inicio, fim: v.fim };
452
+ // B4: o simbolo e procurado pela chave inteira; caminho com `#` nao e cortado no meio.
453
+ const alvo = destino === 'artifact' ? artefatos.get(v.valor)
454
+ : destino === 'file' ? (arquivos.has(v.valor) ? { kind: 'file', path: v.valor, fragment: null } : undefined)
455
+ : e.simbolos.get(v.valor) ?? undefined;
456
+ if (alvo)
457
+ aresta(kind, artefato, alvo, e.extratorMd, 'structured', t);
458
+ else
459
+ lacuna('frontmatter-sem-alvo', path, v.inicio, `${v.chave}: ${v.valor}`);
460
+ }
461
+ }
462
+ mencoes(s.fonte, s.textoDeMencao, (o) => secaoEm(s, arquivo, o));
463
+ }
464
+ for (const f of e.codigo) {
465
+ const arquivo = { kind: 'file', path: f.path, fragment: null };
466
+ mencoes(f, linhasDe(f.texto), () => arquivo);
467
+ }
468
+ return saida;
469
+ }