@orkastery/cli 0.5.0 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/adapters/claude-code/README.md +11 -2
  2. package/adapters/claude-code/agents/ork-master.md +2 -0
  3. package/adapters/claude-code/commands/master.md +8 -0
  4. package/adapters/claude-code/commands/onboarding.md +3 -2
  5. package/adapters/claude-code/commands/ork.md +9 -0
  6. package/adapters/codex/skills/ork/SKILL.md +18 -3
  7. package/adapters/hermes/README.md +1 -0
  8. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  9. package/adapters/hermes/hermes.plugin.json +1 -0
  10. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  11. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
  12. package/adapters/openclaw/README.md +35 -4
  13. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  14. package/adapters/openclaw/dist/index.js +47 -12
  15. package/adapters/openclaw/openclaw.plugin.json +5 -1
  16. package/adapters/openclaw/package.json +2 -2
  17. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  18. package/adapters/openclaw/src/index.ts +50 -12
  19. package/assets/orkmind_bridge.py +117 -9
  20. package/dist/adapters/claude-bg.js +59 -11
  21. package/dist/adapters/codex.js +4 -3
  22. package/dist/agents-md.js +1 -0
  23. package/dist/auditrun.js +6 -2
  24. package/dist/board.js +16 -14
  25. package/dist/branch-de-estado.js +66 -10
  26. package/dist/busca-semantica.js +52 -12
  27. package/dist/canarios-rm057.js +149 -0
  28. package/dist/canarios.js +2 -0
  29. package/dist/ci.js +8 -1
  30. package/dist/claim-lint.js +3 -1
  31. package/dist/claims.js +39 -1
  32. package/dist/company-brain-context.js +158 -22
  33. package/dist/docs.js +110 -3
  34. package/dist/doctor-pulse-cron.js +194 -0
  35. package/dist/doctor-rede.js +149 -0
  36. package/dist/doctor.js +294 -37
  37. package/dist/entrega-pr.js +27 -8
  38. package/dist/esgotamentos.js +181 -0
  39. package/dist/experiencia.js +12 -2
  40. package/dist/fabrica-estado.js +35 -9
  41. package/dist/fabrica-publicar.js +6 -0
  42. package/dist/fechamento.js +20 -66
  43. package/dist/fix.js +3 -3
  44. package/dist/forja.js +31 -7
  45. package/dist/gates.js +5 -1
  46. package/dist/hitl-contract.js +59 -4
  47. package/dist/hitl-curto.js +11 -6
  48. package/dist/hitl-gates.js +5 -0
  49. package/dist/hitl-lote.js +5 -2
  50. package/dist/hitl-resumo.js +33 -1
  51. package/dist/hitl-tempo-parado.js +201 -0
  52. package/dist/hitl-texto-livre.js +5 -5
  53. package/dist/horario.js +27 -0
  54. package/dist/hosts.js +59 -13
  55. package/dist/impedimento.js +205 -0
  56. package/dist/index.js +433 -88
  57. package/dist/indice-vetorial.js +87 -10
  58. package/dist/indice.js +3 -1
  59. package/dist/init.js +28 -5
  60. package/dist/integracoes-locais.js +4 -0
  61. package/dist/intelligence-graph-cli.js +267 -30
  62. package/dist/intelligence-graph-contexto.js +215 -0
  63. package/dist/intelligence-graph-contract.js +71 -12
  64. package/dist/intelligence-graph-extract-md.js +41 -23
  65. package/dist/intelligence-graph-extract-ts.js +181 -11
  66. package/dist/intelligence-graph-extract.js +186 -9
  67. package/dist/intelligence-graph-index.js +205 -24
  68. package/dist/intelligence-graph-parsers.js +6 -4
  69. package/dist/intelligence-graph-repo.js +10 -0
  70. package/dist/leases.js +680 -87
  71. package/dist/ledger-stats.js +15 -1
  72. package/dist/ledger.js +12 -0
  73. package/dist/licoes.js +6 -4
  74. package/dist/liveness.js +2 -1
  75. package/dist/maestro-runtime.js +7 -1
  76. package/dist/manifest.js +44 -1
  77. package/dist/master-nota.js +14 -5
  78. package/dist/master.js +129 -12
  79. package/dist/mcp-artifacts.js +36 -3
  80. package/dist/mcp-git.js +80 -15
  81. package/dist/mcp-grafo-worker.js +93 -0
  82. package/dist/mcp-grafo.js +279 -0
  83. package/dist/mcp-install.js +2 -1
  84. package/dist/mcp-server.js +23 -1
  85. package/dist/mcp-ship.js +2 -0
  86. package/dist/mcp-verify.js +3 -1
  87. package/dist/memoria.js +48 -6
  88. package/dist/network-roadmap.js +195 -20
  89. package/dist/ocupacao.js +17 -5
  90. package/dist/onboarding.js +9 -4
  91. package/dist/orkmind.js +90 -5
  92. package/dist/orquestracao.js +14 -11
  93. package/dist/parado-no-condutor.js +1089 -0
  94. package/dist/phase.js +68 -15
  95. package/dist/policies.js +79 -3
  96. package/dist/portfolio.js +4 -1
  97. package/dist/projeto-alvo.js +5 -1
  98. package/dist/prompts.js +36 -1
  99. package/dist/prova-ativacao.js +414 -0
  100. package/dist/pulse-delivery.js +60 -7
  101. package/dist/pulse.js +99 -3
  102. package/dist/ratelimit.js +20 -4
  103. package/dist/recall.js +3 -1
  104. package/dist/rede-adesao.js +399 -0
  105. package/dist/rede-forja.js +289 -0
  106. package/dist/rede-projetos.js +239 -0
  107. package/dist/rede-status.js +336 -0
  108. package/dist/rede.js +980 -0
  109. package/dist/retry.js +69 -2
  110. package/dist/roadmap-status.js +162 -7
  111. package/dist/runtime-context.js +5 -1
  112. package/dist/runtime-profiles.js +22 -1
  113. package/dist/saida-segura.js +58 -0
  114. package/dist/session-watcher-claude.js +113 -23
  115. package/dist/sessoes-contas.js +187 -0
  116. package/dist/sessoes-inventario.js +6 -12
  117. package/dist/sessoes.js +87 -3
  118. package/dist/ship.js +110 -20
  119. package/dist/thread.js +212 -14
  120. package/dist/util.js +71 -4
  121. package/dist/verify.js +2 -2
  122. package/dist/worktree.js +34 -8
  123. package/eval/casos/onboarding.json +1 -1
  124. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  125. package/package.json +7 -3
  126. package/references/definition-of-done.md +1 -1
  127. package/skills/README.md +1 -1
  128. package/skills/core/onboarding/SKILL.md +3 -2
  129. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  130. package/skills/phases/master-metrics/SKILL.md +8 -3
  131. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -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
  }
@@ -1,7 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OPCOES_TS_DESCRITAS = void 0;
4
+ exports.casaSonda = casaSonda;
4
5
  exports.extrairTypeScript = extrairTypeScript;
6
+ /** Extensoes que o extrator TS le; as do `extensaoDe` do KG2. */
7
+ const EXTENSOES_DO_PROGRAMA = ['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'];
8
+ const DECLARACOES_TS = ['.d.ts', '.d.mts', '.d.cts'];
9
+ /** A base sem a extensao (o TypeScript troca `.js` por `.ts`, e `.d.ts` conta inteira). */
10
+ function semExtensao(base) {
11
+ const nome = base.slice(base.lastIndexOf('/') + 1);
12
+ for (const d of DECLARACOES_TS)
13
+ if (nome.endsWith(d) && nome.length > d.length)
14
+ return base.slice(0, -d.length);
15
+ const i = nome.lastIndexOf('.');
16
+ return i <= 0 ? base : base.slice(0, base.length - (nome.length - i));
17
+ }
18
+ /**
19
+ * RM-031 KG4 (D3): o caminho `p`, novo ou removido, pode mudar uma resolucao de base `base`? O
20
+ * TypeScript e o Node so tentam a base exata, a base com extensao, a base como pasta (`index`,
21
+ * `package.json`) e o mesmo sem a extensao; base vazia (a raiz) casa tudo.
22
+ */
23
+ function casaSonda(base, p) {
24
+ if (base === '')
25
+ return true;
26
+ if (p === base || p.startsWith(`${base}.`) || p.startsWith(`${base}/`))
27
+ return true;
28
+ const sem = semExtensao(base);
29
+ return sem !== base && (p === sem || p.startsWith(`${sem}.`) || p.startsWith(`${sem}/`));
30
+ }
5
31
  /** D2: opcoes fixas do compilador, descritas por nome para entrar no `config_hash`. */
6
32
  exports.OPCOES_TS_DESCRITAS = Object.freeze({
7
33
  allowJs: true, checkJs: false, esModuleInterop: true, jsx: 'preserve', module: 'commonjs', moduleResolution: 'node10',
@@ -68,7 +94,12 @@ function caminhoLiteral(de, especificador) {
68
94
  }
69
95
  return r.join('/');
70
96
  }
71
- function criarHost(ts, e) {
97
+ /**
98
+ * RM-031 KG4 (D3): modulo vazio fora da raiz virtual (nenhum caminho do manifesto o nomeia, e nada o
99
+ * resolve), so para ler o escopo global do checker sem local que esconda global de mesmo nome.
100
+ */
101
+ const TEXTO_DO_SINTETICO = 'export {};\n';
102
+ function criarHost(ts, e, sintetico) {
72
103
  const arquivos = new Set(e.arquivos), dirs = new Set([e.raiz]);
73
104
  for (const p of e.arquivos) {
74
105
  const partes = p.split('/');
@@ -83,6 +114,8 @@ function criarHost(ts, e) {
83
114
  };
84
115
  return {
85
116
  getSourceFile: (nome, alvo) => {
117
+ if (nome === sintetico)
118
+ return ts.createSourceFile(nome, TEXTO_DO_SINTETICO, alvo, true);
86
119
  const p = dentro(nome), t = p === null ? undefined : e.texto(p);
87
120
  return t === undefined ? undefined : ts.createSourceFile(nome, t, alvo, true);
88
121
  },
@@ -109,13 +142,14 @@ function criarHost(ts, e) {
109
142
  };
110
143
  }
111
144
  function extrairTypeScript(e, ts) {
112
- const saida = { nos: [], arestas: [], diagnosticos: [], lacunas: [] };
145
+ const resultado = { unidades: new Map(), globais: [], faltantes: [] };
113
146
  if (e.fontes.length === 0)
114
- return saida;
147
+ return resultado;
115
148
  const absoluto = (p) => `${e.raiz}/${p}`;
116
149
  const relativo = (p) => (p.startsWith(`${e.raiz}/`) ? p.slice(e.raiz.length + 1) : null);
117
- const host = criarHost(ts, e), opts = opcoes(ts);
118
- const programa = ts.createProgram(e.fontes.map((f) => absoluto(f.path)), opts, host);
150
+ const sintetico = `${e.raiz}-escopo-global.ts`;
151
+ const host = criarHost(ts, e, sintetico), opts = opcoes(ts);
152
+ const programa = ts.createProgram([...e.fontes.map((f) => absoluto(f.path)), sintetico], opts, host);
119
153
  const checker = programa.getTypeChecker();
120
154
  const cache = ts.createModuleResolutionCache(e.raiz, (p) => p, opts);
121
155
  const arquivos = new Set(e.arquivos);
@@ -473,41 +507,117 @@ function extrairTypeScript(e, ts) {
473
507
  * lanca erro). Especificador nao relativo vai a node_modules e fica sem alvo. Em ESM o especificador e
474
508
  * URL: so liga o que a URL le como o caminho literal (`urlComoCaminho`).
475
509
  */
510
+ /** KG4 (D3): `bruto` e o caminho que a resolucao do Node achou, antes de conferir se ele carrega. */
476
511
  function alvoNoNode(de, chave) {
477
512
  const esp = especificadorDaChave(chave), formato = formatoDe(de);
478
513
  const relativoAoArquivo = esp === '.' || esp === '..' || esp.startsWith('./') || esp.startsWith('../');
479
514
  if (!relativoAoArquivo || formato === 'falha')
480
- return null;
515
+ return { alvo: null, bruto: null };
481
516
  let alvo;
482
517
  if (chave[0] === 'd' || (chave[0] === 'v' && formato === 'esm'))
483
518
  alvo = urlComoCaminho(esp) ? resolverNode(de, esp, true) : null;
484
519
  else if (chave[0] === 'r' && formato === 'cjs' && !escopoDe(de).invalido)
485
520
  alvo = resolverNode(de, esp, false);
486
521
  else
487
- return null;
488
- return alvo !== null && carregavel(alvo, chave[0] !== 'r') ? alvo : null;
522
+ return { alvo: null, bruto: null };
523
+ return { alvo: alvo !== null && carregavel(alvo, chave[0] !== 'r') ? alvo : null, bruto: alvo };
489
524
  }
525
+ /**
526
+ * KG4 (D3): os nomes de modulo que o TypeScript coleta do arquivo para o programa (import, export,
527
+ * require, import(), tipo importado, inclusive no JSDoc de JavaScript). Sem a lista, nao ha como provar
528
+ * as dependencias e a extracao falha, em vez de reaproveitar sem prova.
529
+ */
530
+ const nomesDeModulo = (sf) => {
531
+ const imports = sf.imports;
532
+ if (!Array.isArray(imports))
533
+ throw new Error('extracao.interna.typescript-sem-imports');
534
+ return imports.map((n) => n.text);
535
+ };
536
+ /** Caminho relativo a raiz por segmentos; `null` se sobe acima da raiz. */
537
+ const normalizar = (partes) => {
538
+ const r = [];
539
+ for (const p of partes) {
540
+ if (p === '' || p === '.')
541
+ continue;
542
+ if (p === '..') {
543
+ if (!r.length)
544
+ return null;
545
+ r.pop();
546
+ }
547
+ else
548
+ r.push(p);
549
+ }
550
+ return r.join('/');
551
+ };
552
+ /** KG4 (D3): base de uma resolucao relativa como o TypeScript a ve (a barra invertida tambem separa); `null` se nao e relativa ou sai da raiz. */
553
+ const baseDaSonda = (de, nome) => (/^\.\.?($|[\\/])/.test(nome) ? normalizar([...de.split('/').slice(0, -1), ...nome.replace(/\\/g, '/').split('/')]) : null);
554
+ /** `/// <reference path>`: relativo ao arquivo, com ou sem `./`. */
555
+ const baseDaReferencia = (de, nome) => (nome.startsWith('/') || nome.startsWith('\\') ? null : normalizar([...de.split('/').slice(0, -1), ...nome.replace(/\\/g, '/').split('/')]));
556
+ /**
557
+ * KG4 (D3): a resolucao de uma pasta le o `package.json` dela, e `main`, `types` e `typings` podem
558
+ * apontar para fora da base: os alvos entram como sondas, tambem com a barra invertida trocada, como o
559
+ * TypeScript le. `typesVersions` remapeia qualquer caminho, e entao a sonda casa tudo; o mesmo vale
560
+ * para `package.json` que nao e JSON estrito (CHECK, A1: o TypeScript aceita comentario e virgula
561
+ * final). Mudanca no proprio `package.json` extrai o TypeScript inteiro.
562
+ */
563
+ const sondasDoPacote = (base, sondas) => {
564
+ for (const pasta of new Set([base, semExtensao(base)])) {
565
+ const arquivo = junta(pasta, 'package.json');
566
+ if (!arquivos.has(arquivo))
567
+ continue;
568
+ const v = lerJson(arquivo);
569
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) {
570
+ sondas.add('');
571
+ continue;
572
+ }
573
+ const o = v;
574
+ if ('typesVersions' in o)
575
+ sondas.add('');
576
+ for (const campo of ['main', 'types', 'typings']) {
577
+ const alvo = o[campo];
578
+ if (typeof alvo !== 'string' || !alvo)
579
+ continue;
580
+ for (const forma of new Set([alvo, alvo.replace(/\\/g, '/')])) {
581
+ const t = forma.startsWith('/') ? null : caminhoLiteral(arquivo, `./${forma}`);
582
+ if (t !== null)
583
+ sondas.add(t);
584
+ }
585
+ }
586
+ }
587
+ };
588
+ const sondar = (base, sondas) => {
589
+ if (base === null)
590
+ return;
591
+ sondas.add(base);
592
+ sondasDoPacote(base, sondas);
593
+ };
490
594
  // Primeira passada, em todos os arquivos: o alvo de cada especificador para o compilador e para o
491
595
  // runtime. Onde divergem, a aresta de import vai ao que roda e nenhum simbolo passa por ali (D3).
492
596
  const resolucoes = new Map();
493
597
  const divergentesGlobais = new Set();
598
+ // KG4 (D3): por fonte, os caminhos a que as referencias de modulo resolvem e as bases das relativas.
599
+ const dependenciasDe = new Map(), sondasDe = new Map();
494
600
  for (const fonte of e.fontes) {
495
601
  const sf = programa.getSourceFile(absoluto(fonte.path));
496
602
  if (!sf || sf.fileName !== absoluto(fonte.path))
497
603
  continue;
498
604
  const ext = extensao(fonte.path), mapa = new Map();
605
+ const dependencias = new Set(), sondas = new Set();
499
606
  for (const chave of especificadoresEm(sf)) {
500
607
  if (mapa.has(chave))
501
608
  continue;
502
609
  const esp = especificadorDaChave(chave), doCompilador = resolver(esp, fonte.path);
610
+ sondar(baseDaSonda(fonte.path, esp), sondas);
503
611
  let alvo = doCompilador, divergente = false;
504
612
  // Import so de tipo nao roda: vale o que o compilador liga. Fonte JavaScript roda no Node, e onde
505
613
  // o Node falha (formato, escopo, alvo que nao carrega) nao ha aresta.
506
614
  if (chave[0] !== 't' && EXTENSOES_JS.includes(ext)) {
507
615
  const runtime = alvoNoNode(fonte.path, chave);
508
- if (runtime !== doCompilador) {
616
+ if (runtime.bruto !== null)
617
+ dependencias.add(runtime.bruto);
618
+ if (runtime.alvo !== doCompilador) {
509
619
  divergente = true;
510
- alvo = runtime;
620
+ alvo = runtime.alvo;
511
621
  }
512
622
  }
513
623
  const impl = chave[0] !== 't' && !divergente && doCompilador !== null ? implementacaoDe(doCompilador) : null;
@@ -515,11 +625,45 @@ function extrairTypeScript(e, ts) {
515
625
  divergente = true;
516
626
  alvo = impl;
517
627
  }
628
+ for (const x of [doCompilador, alvo])
629
+ if (x !== null)
630
+ dependencias.add(x);
518
631
  mapa.set(chave, { alvo, divergente, doCompilador });
519
632
  if (divergente)
520
633
  divergentesGlobais.add(`${fonte.path}\u0000${chave}`);
521
634
  }
635
+ // O que o TypeScript coleta e a varredura nao ve (tipo importado no JSDoc) tambem liga o checker a outro arquivo.
636
+ for (const nome of nomesDeModulo(sf)) {
637
+ sondar(baseDaSonda(fonte.path, nome), sondas);
638
+ const alvo = resolver(nome, fonte.path);
639
+ if (alvo !== null)
640
+ dependencias.add(alvo);
641
+ }
642
+ for (const r of sf.referencedFiles) {
643
+ const base = baseDaReferencia(fonte.path, r.fileName);
644
+ if (base === null)
645
+ continue;
646
+ sondas.add(base);
647
+ for (const x of [base, `${base}.ts`, `${base}.tsx`, `${base}.d.ts`, `${base}.js`, `${base}.jsx`])
648
+ if (arquivos.has(x))
649
+ dependencias.add(x);
650
+ }
522
651
  resolucoes.set(fonte.path, mapa);
652
+ dependenciasDe.set(fonte.path, dependencias);
653
+ sondasDe.set(fonte.path, sondas);
654
+ }
655
+ // KG4 (D3): no programa parcial, todo alvo TS/JS das fontes precisa ser fonte, para ter formato e
656
+ // resolucoes; senao o caminho cresce e a extracao recomeca.
657
+ if (e.emitir) {
658
+ const noPrograma = new Set(e.fontes.map((f) => f.path)), faltantes = new Set();
659
+ for (const dependencias of dependenciasDe.values()) {
660
+ for (const d of dependencias) {
661
+ if (!noPrograma.has(d) && EXTENSOES_DO_PROGRAMA.includes(extensao(d)) && e.texto(d) !== undefined)
662
+ faltantes.add(d);
663
+ }
664
+ }
665
+ if (faltantes.size)
666
+ return { ...resultado, faltantes: [...faltantes].sort() };
523
667
  }
524
668
  // B-N2: modulo que importa, direto ou por outro modulo, um arquivo com import divergente pode reexportar
525
669
  // o que o compilador liga e o runtime nao (`export *`, `module.exports = require(...)`). O fecho segue
@@ -577,6 +721,13 @@ function extrairTypeScript(e, ts) {
577
721
  return false;
578
722
  }
579
723
  for (const fonte of e.fontes) {
724
+ if (e.emitir && !e.emitir.has(fonte.path))
725
+ continue;
726
+ // KG4 (D2): os achados ficam por arquivo, com as dependencias e as sondas, para as unidades do indice.
727
+ const saida = { nos: [], arestas: [], diagnosticos: [], lacunas: [] };
728
+ resultado.unidades.set(fonte.path, {
729
+ dependencias: [...(dependenciasDe.get(fonte.path) ?? [])].sort(), sondas: [...(sondasDe.get(fonte.path) ?? [])].sort(), achados: saida,
730
+ });
580
731
  const sf = programa.getSourceFile(absoluto(fonte.path));
581
732
  // Fonte que o compilador trocou por outra (pacote duplicado) nao e lida: o no seria do outro arquivo.
582
733
  if (!sf || sf.fileName !== absoluto(fonte.path)) {
@@ -806,5 +957,24 @@ function extrairTypeScript(e, ts) {
806
957
  for (const st of sf.statements)
807
958
  visitar(st, null);
808
959
  }
809
- return saida;
960
+ // KG4 (D3): quem declara no escopo global (visto de um modulo vazio, sem local que o esconda) ou
961
+ // aumenta modulo alcanca arquivo que nao o importa; mudanca nele reextrai o TypeScript inteiro.
962
+ const doEscopoGlobal = programa.getSourceFile(sintetico);
963
+ if (!doEscopoGlobal)
964
+ throw new Error('extracao.interna.escopo-global');
965
+ const globais = new Set();
966
+ for (const simbolo of checker.getSymbolsInScope(doEscopoGlobal, -1)) {
967
+ for (const d of simbolo.declarations ?? []) {
968
+ const p = relativo(d.getSourceFile().fileName);
969
+ if (p !== null)
970
+ globais.add(p);
971
+ }
972
+ }
973
+ for (const sf of programa.getSourceFiles()) {
974
+ const aumentos = sf.moduleAugmentations, p = relativo(sf.fileName);
975
+ if (p !== null && aumentos && aumentos.length)
976
+ globais.add(p);
977
+ }
978
+ resultado.globais = [...globais].sort();
979
+ return resultado;
810
980
  }