@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
package/dist/docs.js CHANGED
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.LIMITE_DO_PARAGRAFO = exports.LIMITE_DA_FRASE = exports.CATEGORIAS = exports.DIMENSOES = exports.ESTADOS_DA_ESPECIFICACAO = exports.DIR_PADROES = exports.DIR_ROADMAP = exports.DIR_PRODUTO = void 0;
37
37
  exports.separarFrontmatter = separarFrontmatter;
38
+ exports.ehPaginaDeDocs = ehPaginaDeDocs;
39
+ exports.documentoDeTexto = documentoDeTexto;
38
40
  exports.carregarDocs = carregarDocs;
39
41
  exports.resolverBase = resolverBase;
40
42
  exports.comandosDaAjuda = comandosDaAjuda;
@@ -44,6 +46,8 @@ exports.renderizarEstado = renderizarEstado;
44
46
  exports.textoDaVerificacao = textoDaVerificacao;
45
47
  exports.escreverYaml = escreverYaml;
46
48
  exports.mergeDaThread = mergeDaThread;
49
+ exports.mergesDaThreadNoGit = mergesDaThreadNoGit;
50
+ exports.escopoPadraoDoSync = escopoPadraoDoSync;
47
51
  exports.sincronizarDocs = sincronizarDocs;
48
52
  exports.tabelaDoRoadmap = tabelaDoRoadmap;
49
53
  exports.tabelaDoProduto = tabelaDoProduto;
@@ -136,38 +140,49 @@ function separarFrontmatter(texto) {
136
140
  return { bruto: null, corpo: t };
137
141
  return { bruto: t.slice(4, fim + 1), corpo: t.slice(fim + 5) };
138
142
  }
143
+ /** `_modelo-*.md` e `README.md` sao modelo e indice, nao paginas de entidade. */
144
+ function ehPaginaDeDocs(nome) {
145
+ return nome.endsWith('.md') && !nome.startsWith('_') && nome !== 'README.md';
146
+ }
139
147
  function paginas(raiz, dir) {
140
148
  const abs = path.join(raiz, dir);
141
149
  if (!fs.existsSync(abs))
142
150
  return [];
143
- // `_modelo-*.md` e `README.md` sao modelo e indice, nao paginas de entidade.
144
151
  return fs.readdirSync(abs)
145
- .filter((n) => n.endsWith('.md') && !n.startsWith('_') && n !== 'README.md')
152
+ .filter(ehPaginaDeDocs)
146
153
  .sort()
147
154
  .map((n) => `${dir}/${n}`);
148
155
  }
156
+ /**
157
+ * RM-054: uma pagina a partir do texto, venha ele do disco, de um blob do git ou da forja. O
158
+ * achado e o mesmo que a leitura do disco daria.
159
+ */
160
+ function documentoDeTexto(arquivo, texto) {
161
+ const { bruto, corpo } = separarFrontmatter(texto);
162
+ if (bruto === null) {
163
+ return { achado: erro(arquivo, undefined, 'docs.frontmatter.ausente', 'página sem frontmatter YAML', 'comece o arquivo com as chaves do modelo entre duas linhas ---') };
164
+ }
165
+ let dados;
166
+ try {
167
+ dados = (0, yaml_1.lerYaml)(bruto);
168
+ }
169
+ catch (e) {
170
+ return { achado: erro(arquivo, undefined, 'docs.frontmatter.invalido', `frontmatter inválido: ${e.message}`) };
171
+ }
172
+ if (!ehMapa(dados)) {
173
+ return { achado: erro(arquivo, undefined, 'docs.frontmatter.invalido', 'o frontmatter precisa ser um mapa de chaves') };
174
+ }
175
+ return { doc: { arquivo, tipo: dados.tipo, id: String(dados.id ?? ''), dados, corpo } };
176
+ }
149
177
  function carregarDocs(raiz) {
150
178
  const docs = [];
151
179
  const achados = [];
152
180
  for (const arquivo of [...paginas(raiz, exports.DIR_PRODUTO), ...paginas(raiz, exports.DIR_ROADMAP)]) {
153
- const { bruto, corpo } = separarFrontmatter(fs.readFileSync(path.join(raiz, arquivo), 'utf8'));
154
- if (bruto === null) {
155
- achados.push(erro(arquivo, undefined, 'docs.frontmatter.ausente', 'página sem frontmatter YAML', 'comece o arquivo com as chaves do modelo entre duas linhas ---'));
156
- continue;
157
- }
158
- let dados;
159
- try {
160
- dados = (0, yaml_1.lerYaml)(bruto);
161
- }
162
- catch (e) {
163
- achados.push(erro(arquivo, undefined, 'docs.frontmatter.invalido', `frontmatter inválido: ${e.message}`));
164
- continue;
165
- }
166
- if (!ehMapa(dados)) {
167
- achados.push(erro(arquivo, undefined, 'docs.frontmatter.invalido', 'o frontmatter precisa ser um mapa de chaves'));
168
- continue;
169
- }
170
- docs.push({ arquivo, tipo: dados.tipo, id: String(dados.id ?? ''), dados, corpo });
181
+ const lido = documentoDeTexto(arquivo, fs.readFileSync(path.join(raiz, arquivo), 'utf8'));
182
+ if ('doc' in lido)
183
+ docs.push(lido.doc);
184
+ else
185
+ achados.push(lido.achado);
171
186
  }
172
187
  return { docs, achados };
173
188
  }
@@ -248,9 +263,12 @@ function verificarDocs(raiz, opcoes = {}) {
248
263
  verificarFontes(raiz, d, comandos, achados);
249
264
  if (d.tipo === 'roadmap') {
250
265
  verificarEstadoDoRoadmap(raiz, d, base, achados);
266
+ if (base)
267
+ verificarMergeDaThread(raiz, d, base, achados, opcoes.pr === true);
251
268
  verificarTabelasDeEstado(d, achados);
252
269
  }
253
270
  }
271
+ verificarIndices(raiz, docs, achados, opcoes.pr === true);
254
272
  return { docs, achados };
255
273
  }
256
274
  function verificarChaves(d, achados) {
@@ -550,6 +568,57 @@ function verificarEstadoDoRoadmap(raiz, d, base, achados) {
550
568
  }
551
569
  }
552
570
  }
571
+ /**
572
+ * RM-037 (fatia 3, defeito 1): o outro sentido da paridade com o git. Depois do merge do PR, o
573
+ * frontmatter da RM-049 (#26) e da RM-051 (#33) seguiu em "Branch criada", e o verificador dizia OK:
574
+ * ele so conferia que `Mesclado` tem o commit na base. O merge desta fabrica e pelo GitHub, sem o
575
+ * `ork ship` no caminho, entao a prova mora aqui, no job que o CI obrigatorio roda. A regra usa o
576
+ * mesmo `ship(<thread>)` do `sincronizarDocs`: tudo o que ela acusa o `sincronizar` corrige.
577
+ */
578
+ function verificarMergeDaThread(raiz, d, base, achados, pr = false) {
579
+ const sdlc = ehMapa(d.dados.sdlc) ? d.dados.sdlc : null;
580
+ const thread = sdlc && typeof sdlc.thread === 'string' ? sdlc.thread.trim() : '';
581
+ if (!thread)
582
+ return;
583
+ const merges = mergesDaThreadNoGit(raiz, thread, base);
584
+ if (merges.length === 0)
585
+ return;
586
+ const codigo = String((ehMapa(d.dados.estado) ? d.dados.estado.codigo : null) ?? '');
587
+ if (codigo === 'Mesclado')
588
+ return;
589
+ achados.push((pr ? aviso : erro)(d.arquivo, d.id, 'docs.paridade.merge', `a thread ${thread} entrou na ${base} pelo merge ${merges[0].slice(0, 7)} (ship(${thread})), e o estado.codigo diz "${codigo || 'vazio'}"` +
590
+ (pr ? ' (no PR, aviso: o push da main reprova)' : ''), `abra um PR de docs sobre a ${base} atualizada com ork docs sincronizar --escrever --so ${d.id} (o item e os índices); ` +
591
+ `se o item tem outra fatia em curso, aponte sdlc.thread para a thread dela`));
592
+ }
593
+ /** Os indices gerados (`docs/roadmap/README.md`, `docs/produto/README.md`) e o que o frontmatter diz. */
594
+ const INDICES_GERADOS = [
595
+ [`${exports.DIR_ROADMAP}/README.md`, (docs) => tabelaDoRoadmap(docs)],
596
+ [`${exports.DIR_PRODUTO}/README.md`, (docs) => tabelaDoProduto(docs)],
597
+ ];
598
+ /**
599
+ * RM-037 (fatia 3, defeito 1): o indice e uma vista do frontmatter, como as tabelas do corpo, e mentia
600
+ * do mesmo jeito: sem a RM-051 e com a RM-031 de antes. Divergente reprova; vazio (o modelo recem-copiado
601
+ * pelo `ork docs init`) so avisa; sem os marcadores, nao ha indice gerado para conferir.
602
+ */
603
+ function verificarIndices(raiz, docs, achados, pr = false) {
604
+ for (const [arquivo, gerar] of INDICES_GERADOS) {
605
+ const abs = path.join(raiz, arquivo);
606
+ if (!fs.existsSync(abs))
607
+ continue;
608
+ // Checkout com CRLF (Windows com core.autocrlf) nao e divergencia: as paginas tambem normalizam.
609
+ const atual = blocoEntre(fs.readFileSync(abs, 'utf8').replace(/\r\n/g, '\n'), INICIO, FIM);
610
+ if (atual === null)
611
+ continue;
612
+ if (atual === '') {
613
+ if (docs.length > 0) {
614
+ achados.push(aviso(arquivo, undefined, 'docs.paridade.indice', 'o índice ainda não foi gerado', 'rode ork docs sincronizar --escrever'));
615
+ }
616
+ }
617
+ else if (atual !== gerar(docs)) {
618
+ achados.push((pr ? aviso : erro)(arquivo, undefined, 'docs.paridade.indice', `o índice diverge do frontmatter das páginas${pr ? ' (no PR, aviso: o push da main reprova)' : ''}`, 'o índice é gerado do frontmatter: rode ork docs sincronizar --escrever e commite o índice'));
619
+ }
620
+ }
621
+ }
553
622
  // --------------------------------------------------------------------------------------------
554
623
  // Saida para humano (TDAH) e para agente
555
624
  function textoDaVerificacao(docs, achados) {
@@ -652,24 +721,65 @@ function mergeDaThread(raiz, thread, base) {
652
721
  return { sha: String(ship.mergeSha).slice(0, 7), fonte: 'ledger ship_done' };
653
722
  }
654
723
  catch { /* thread sem estado nesta maquina: cai para o git */ }
655
- // Sem `-n 1`: o git corta antes de inverter, e `--reverse -n 1` devolveria o mais recente.
656
- const log = git(raiz, ['log', base, '--reverse', '--format=%H', '--fixed-strings', `--grep=ship(${thread})`]);
657
- const primeiro = log.ok ? log.saida.split('\n')[0] : '';
724
+ const primeiro = mergesDaThreadNoGit(raiz, thread, base)[0];
658
725
  if (primeiro)
659
726
  return { sha: primeiro.slice(0, 7), fonte: `git log ${base}` };
660
727
  return null;
661
728
  }
729
+ /**
730
+ * Os commits `ship(<thread>)` da base, do mais velho ao mais novo, so pelo git (o CI nao tem ledger).
731
+ * RM-037 (fatia 3): um lugar so para o `sincronizarDocs` e o `verificarDocs` acharem o mesmo merge.
732
+ */
733
+ function mergesDaThreadNoGit(raiz, thread, base) {
734
+ // Sem `-n 1`: o git corta antes de inverter, e `--reverse -n 1` devolveria o mais recente.
735
+ // GO-FIX 2 (achado 4 do CHECK): so a linha de primeiro pai da base e o assunto que COMECA com
736
+ // `ship(<thread>)`, como no `mergeDaEntrega`. Commit que cita o merge no corpo, ou o Revert dele, nao e merge.
737
+ const log = git(raiz, ['log', base, '--first-parent', '--reverse', '--format=%H%x09%s', '--fixed-strings', `--grep=ship(${thread})`]);
738
+ if (!log.ok)
739
+ return [];
740
+ return log.saida.split('\n').map((l) => l.split('\t'))
741
+ .filter(([sha, assunto]) => !!sha && typeof assunto === 'string' && assunto.startsWith(`ship(${thread})`))
742
+ .map(([sha]) => sha);
743
+ }
662
744
  function temBranch(raiz, thread) {
663
745
  const r = git(raiz, ['for-each-ref', '--format=%(refname:short)', `refs/heads/ork/${thread}-*`, `refs/remotes/origin/ork/${thread}-*`]);
664
746
  return r.ok && r.saida.length > 0;
665
747
  }
666
748
  const ORDEM_DO_CODIGO = exports.DIMENSOES.codigo;
749
+ /**
750
+ * RM-037 (rm037noite, defeito 5): o escopo padrao do `ork docs sincronizar`. Na worktree de uma thread
751
+ * com item do roadmap, so o item dela: da worktree da ork-rm037noite, o sincronizar gravava RM-025,
752
+ * 026, 031, 038, 048, 049, 050 e 054, e cada PR conflitava com as outras. Fora de worktree de thread
753
+ * (a raiz, onde o condutor sincroniza depois do merge), todo item, como antes.
754
+ */
755
+ function escopoPadraoDoSync(raiz) {
756
+ const real = (p) => { try {
757
+ return fs.realpathSync(p);
758
+ }
759
+ catch {
760
+ return path.resolve(p);
761
+ } };
762
+ const aqui = real(raiz);
763
+ for (const id of (0, thread_1.listarIds)(raiz)) {
764
+ let t;
765
+ try {
766
+ t = (0, thread_1.lerThread)(raiz, id);
767
+ }
768
+ catch {
769
+ continue;
770
+ }
771
+ if (t.worktree && real(t.worktree) === aqui)
772
+ return { itens: t.roadmap ? [t.roadmap] : null, thread: t.id };
773
+ }
774
+ return { itens: null, thread: null };
775
+ }
667
776
  function sincronizarDocs(raiz, opcoes = {}) {
668
777
  const base = resolverBase(raiz, opcoes.baseBranch) ?? (opcoes.baseBranch ?? 'main');
669
778
  const agora = opcoes.agora ?? (() => isoComFusoLocal(new Date()));
670
779
  const { docs } = carregarDocs(raiz);
671
780
  const mudancas = [];
672
- for (const d of docs.filter((x) => x.tipo === 'roadmap')) {
781
+ const noEscopo = (d) => !opcoes.itens || opcoes.itens.includes(d.id);
782
+ for (const d of docs.filter((x) => x.tipo === 'roadmap' && noEscopo(x))) {
673
783
  const sdlc = ehMapa(d.dados.sdlc) ? d.dados.sdlc : null;
674
784
  const thread = sdlc && typeof sdlc.thread === 'string' ? sdlc.thread : null;
675
785
  if (!thread)
@@ -715,7 +825,7 @@ function sincronizarDocs(raiz, opcoes = {}) {
715
825
  }
716
826
  // As tabelas de estado do corpo sao vistas do frontmatter: regeradas em todo item de roadmap,
717
827
  // inclusive quando a pessoa mudou o frontmatter a mao (ciclo, deploy, exposicao...).
718
- for (const d of docs.filter((x) => x.tipo === 'roadmap')) {
828
+ for (const d of docs.filter((x) => x.tipo === 'roadmap' && noEscopo(x))) {
719
829
  const novo = substituirBloco(substituirBloco(d.corpo, RELANCE_INI, RELANCE_FIM, renderizarRelance(d)), ESTADO_INI, ESTADO_FIM, renderizarEstado(d));
720
830
  if (novo !== d.corpo) {
721
831
  if (!mudancas.some((m) => m.id === d.id)) {
package/dist/doctor.js CHANGED
@@ -39,8 +39,14 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.TETO_DO_DONO_DO_GIT = void 0;
42
43
  exports.checarContas = checarContas;
44
+ exports.checarChaveDeEmbedding = checarChaveDeEmbedding;
45
+ exports.checarDonoDoGit = checarDonoDoGit;
43
46
  exports.checarOnboarding = checarOnboarding;
47
+ exports.blocosDoRuntime = blocosDoRuntime;
48
+ exports.checarRuntimeClaude = checarRuntimeClaude;
49
+ exports.checarDespachoPeloCodex = checarDespachoPeloCodex;
44
50
  exports.checar = checar;
45
51
  exports.relatorio = relatorio;
46
52
  exports.doctor = doctor;
@@ -62,6 +68,7 @@ const project_state_1 = require("./project-state");
62
68
  const runtime_ambiente_1 = require("./runtime-ambiente");
63
69
  const runtime_profiles_1 = require("./runtime-profiles");
64
70
  const preflight_1 = require("./preflight");
71
+ const setup_1 = require("./setup");
65
72
  /**
66
73
  * I-33 (D7): check "contas por runtime". Cada perfil ativo tem o login conferido pelo proprio
67
74
  * CLI com o env dele (`claude auth status`, `codex login status`); o doctor so le e relata, sem
@@ -116,6 +123,112 @@ const ENVS_QUE_REDIRECIONAM = [
116
123
  function versaoNode() {
117
124
  return Number(process.versions.node.split('.')[0]);
118
125
  }
126
+ /**
127
+ * I-38 (T7): a chave de embedding aparece pelo NOME, nunca pelo valor. Ausente e aviso (a busca
128
+ * cai para o fallback local ou para FTS e o regime segue); nome de provider pago e falha, porque a
129
+ * entrada do `ork` apagaria a variavel sob `subscription-only` e o preflight reprovaria o codex.
130
+ */
131
+ function checarChaveDeEmbedding(carregado, env = process.env) {
132
+ const nome = 'chave de embedding';
133
+ const config = (0, manifest_1.configDeEmbedding)(carregado.manifesto);
134
+ if (config.provider === 'none')
135
+ return { nome, nivel: 'ok', detalhe: 'memory.embedding com provider none: busca por significado desligada' };
136
+ const variavel = config.api_key_env;
137
+ if (runtime_ambiente_1.ENVS_DE_PROVIDER_PAGO.includes(variavel)) {
138
+ return { nome, nivel: 'fail', detalhe: `memory.embedding.api_key_env declara ${variavel}, nome da lista de provider pago`,
139
+ correcao: 'use uma chave dedicada ao Orkastery com nome proprio, por exemplo ORKASTERY_EMBEDDING_API_KEY' };
140
+ }
141
+ if (!variavel) {
142
+ return { nome, nivel: 'fail', detalhe: `memory.embedding com provider ${config.provider} sem api_key_env`,
143
+ correcao: 'declare em memory.embedding.api_key_env o NOME da variavel com a chave dedicada' };
144
+ }
145
+ const valor = (env[variavel] ?? '').trim();
146
+ const dsn = carregado.manifesto.memory.database_url_env ? (env[carregado.manifesto.memory.database_url_env] ?? '') : '';
147
+ if (valor !== '' && !(0, orkmind_1.chaveDeEmbeddingAceita)(valor, dsn)) {
148
+ return { nome, nivel: 'fail', detalhe: `${variavel} tem valor recusado: parece URL, DSN ou texto com espaco (valor nunca impresso)`,
149
+ correcao: `confira se ${variavel} guarda a chave dedicada, e nao outra credencial` };
150
+ }
151
+ if (valor !== '') {
152
+ return { nome, nivel: 'ok', detalhe: `${variavel} presente no ambiente (valor nunca impresso); ${config.provider} ${config.model}` };
153
+ }
154
+ return { nome, nivel: 'warn', detalhe: `${variavel} ausente do ambiente: a busca por significado usa o fallback local ou cai para FTS; o regime nao muda`,
155
+ correcao: `exporte ${variavel} com a chave dedicada ao Orkastery (com limite de credito no painel do provider)` };
156
+ }
157
+ /** Acima disso a conferencia do dono do `.git` fica parcial (aviso), para o doctor nao pesar. */
158
+ exports.TETO_DO_DONO_DO_GIT = 200_000;
159
+ /**
160
+ * RM-037 (fatia 3, defeito 7): git rodado como root no repositorio do dono deixa objeto, pasta e
161
+ * `FETCH_HEAD` com outro dono, e o git do dono deixa de gravar neles: o fetch travou em 29/09 e em
162
+ * 01/10. O dono do repositorio e o dono do diretorio comum do git. O check acusa com a contagem, ate
163
+ * tres exemplos e o `chown` exato; o doctor nunca roda nada. Fora de repositorio git, `null`.
164
+ */
165
+ function checarDonoDoGit(dirInicial, opcoes = {}) {
166
+ const nome = 'dono do .git';
167
+ const comum = (0, util_1.exec)('git', ['rev-parse', '--git-common-dir'], dirInicial);
168
+ if (!comum.ok || !comum.stdout.trim())
169
+ return null;
170
+ const dirComum = path.resolve(dirInicial, comum.stdout.trim());
171
+ const lstat = opcoes.lstat ?? ((p) => fs.lstatSync(p));
172
+ let raiz;
173
+ try {
174
+ raiz = lstat(dirComum);
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ const teto = opcoes.teto ?? exports.TETO_DO_DONO_DO_GIT;
180
+ const relativo = (f) => path.relative(path.dirname(dirComum), f);
181
+ // A correcao sai pronta para copiar: caminho com caractere fora do conjunto seguro vai entre aspas simples.
182
+ const noShell = (s) => /^[A-Za-z0-9._/-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
183
+ const estranhos = [], uids = new Set();
184
+ let total = 0, vistos = 0, parcial = false;
185
+ const pilha = [dirComum];
186
+ while (pilha.length > 0 && !parcial) {
187
+ const atual = pilha.pop();
188
+ let nomes;
189
+ // Pasta que o dono nao consegue listar ja aparece pelo dono dela, na entrada de cima.
190
+ try {
191
+ nomes = fs.readdirSync(atual);
192
+ }
193
+ catch {
194
+ continue;
195
+ }
196
+ for (const n of nomes) {
197
+ if (++vistos > teto) {
198
+ parcial = true;
199
+ break;
200
+ }
201
+ const f = path.join(atual, n);
202
+ let st;
203
+ try {
204
+ st = lstat(f);
205
+ }
206
+ catch {
207
+ continue;
208
+ }
209
+ if (st.uid !== raiz.uid) {
210
+ total++;
211
+ uids.add(st.uid);
212
+ if (estranhos.length < 3)
213
+ estranhos.push(relativo(f));
214
+ }
215
+ if (st.isDirectory())
216
+ pilha.push(f);
217
+ }
218
+ }
219
+ if (total > 0) {
220
+ return { nome, nivel: 'fail',
221
+ detalhe: `${total} entrada(s) de ${relativo(dirComum)} com outro dono (uid ${[...uids].sort((a, b) => a - b).join(', ')}; o do ` +
222
+ `repositorio e ${raiz.uid}): ${estranhos.join(', ')}${total > estranhos.length ? ', ...' : ''}; o git do dono nao grava nelas, ` +
223
+ `e o fetch e o commit falham${parcial ? ` (conferencia parcial: mais de ${teto} entradas)` : ''}`,
224
+ correcao: `sudo chown -R ${raiz.uid}:${raiz.gid} ${noShell(dirComum)} (o ork nao roda isso sozinho)` };
225
+ }
226
+ if (parcial) {
227
+ return { nome, nivel: 'warn', detalhe: `mais de ${teto} entradas em ${relativo(dirComum)}: conferencia parcial, sem outro dono ate aqui`,
228
+ correcao: `confira o resto com: find ${noShell(dirComum)} -not -uid ${raiz.uid}` };
229
+ }
230
+ return { nome, nivel: 'ok', detalhe: `${vistos} entradas de ${relativo(dirComum)} com o dono do repositorio (uid ${raiz.uid})` };
231
+ }
119
232
  /** Checks locais; ler a entrevista não abre driver, banco, runtime ou rede. */
120
233
  function checarOnboarding(carregado) {
121
234
  const estado = (0, onboarding_1.lerOnboarding)(carregado.raiz);
@@ -131,9 +244,15 @@ function checarOnboarding(carregado) {
131
244
  checks.push({ nome: 'onboarding memoria', nivel: 'warn', detalhe: `entrevista escolheu ${modo}; manifesto declara ${carregado.manifesto.memory.mode}`,
132
245
  correcao: `revise memory.mode: ${modo} em orkastery.yaml; a entrevista não altera o manifesto` });
133
246
  }
134
- // I-35: o fuso respondido na etapa maestro orienta owner.timezone, sem editar o manifesto.
247
+ // I-35: o fuso respondido na etapa maestro orienta owner.timezone, sem editar o manifesto. Com
248
+ // `owner.timezone` na mesma resposta (gravado no manifesto pelo `onboarding set`), vale ele: no
249
+ // ensaio da 0.5.0, o `fuso` legado da resposta anterior mandava desfazer a escolha explicita.
135
250
  const maestro = estado.etapas.maestro?.conteudo;
136
- const fusoDaEntrevista = maestro && typeof maestro === 'object' && !Array.isArray(maestro) ? (0, horario_1.normalizarFuso)(maestro.fuso) : undefined;
251
+ const owner = maestro && typeof maestro === 'object' && !Array.isArray(maestro) ? maestro.owner : undefined;
252
+ const fusoDoOwner = owner && typeof owner === 'object' && !Array.isArray(owner) ? owner.timezone : undefined;
253
+ // Owner invalido (so por edicao a mao: o `onboarding set` recusa) nao cala o fuso legado.
254
+ const fusoDaEntrevista = maestro && typeof maestro === 'object' && !Array.isArray(maestro)
255
+ ? (0, horario_1.normalizarFuso)(fusoDoOwner) ?? (0, horario_1.normalizarFuso)(maestro.fuso) : undefined;
137
256
  if (fusoDaEntrevista && fusoDaEntrevista !== carregado.manifesto.owner?.timezone) {
138
257
  checks.push({ nome: 'onboarding fuso', nivel: 'warn',
139
258
  detalhe: `entrevista informou ${fusoDaEntrevista}; manifesto declara ${carregado.manifesto.owner?.timezone ?? `${horario_1.CHAVE_DO_FUSO} ausente`}`,
@@ -141,6 +260,97 @@ function checarOnboarding(carregado) {
141
260
  }
142
261
  return checks;
143
262
  }
263
+ /**
264
+ * Fatia 2 do ensaio da 0.5.0 (P1): por qual runtime cada bloco dos modos de
265
+ * `conduction.allowed_modes` despacha, pelo mesmo criterio do despacho sem opcao de CLI
266
+ * (`resolverDespacho`: o bloco do setup vence `runtime.adapter`). Quem passou os blocos para o
267
+ * Codex nao precisa do `claude`. Setup ilegivel devolve `null`: quem chama fica no lado seguro.
268
+ */
269
+ function blocosDoRuntime(carregado, runtime) {
270
+ const { manifesto } = carregado;
271
+ const r = { principal: [], fallback: [] };
272
+ try {
273
+ const setup = (0, setup_1.lerSetup)(carregado.raiz);
274
+ for (const modo of manifesto.conduction.allowed_modes) {
275
+ modos_1.MODOS[modo].blocos.forEach((b, i) => {
276
+ const doBloco = (0, setup_1.configDoBloco)(setup, modo, b.fases[0]);
277
+ const bloco = { tag: modos_1.MODOS[modo].tag, bloco: i + 1 };
278
+ if ((doBloco?.runtime ?? manifesto.runtime.adapter) === runtime)
279
+ r.principal.push(bloco);
280
+ else if (doBloco?.fallback?.some((f) => f.split(':')[0] === runtime))
281
+ r.fallback.push(bloco);
282
+ });
283
+ }
284
+ }
285
+ catch {
286
+ return null;
287
+ }
288
+ return r;
289
+ }
290
+ /** `#Classic 1, 2; #Auto 1`: os blocos agrupados por modo, na ordem dos modos permitidos. */
291
+ function textoDosBlocos(blocos) {
292
+ const porModo = new Map();
293
+ for (const b of blocos)
294
+ porModo.set(b.tag, [...(porModo.get(b.tag) ?? []), b.bloco]);
295
+ return [...porModo].map(([tag, numeros]) => `${tag} ${numeros.join(', ')}`).join('; ');
296
+ }
297
+ /**
298
+ * Fatia 2 do ensaio da 0.5.0 (P1): o `claude` so e obrigatorio quando algum bloco de modo permitido
299
+ * despacha por ele. Sem manifesto, com manifesto invalido ou com setup ilegivel vale o padrao (todo
300
+ * bloco no claude-bg), e a falta reprova como antes.
301
+ */
302
+ function checarRuntimeClaude(carregado, claude, versao) {
303
+ const nome = 'runtime claude-bg';
304
+ if (claude)
305
+ return { nome, nivel: 'ok', detalhe: `${claude} (${versao ?? 'versao desconhecida'})` };
306
+ const blocos = carregado && carregado.erros.length === 0 ? blocosDoRuntime(carregado, 'claude-bg') : null;
307
+ if (blocos && blocos.principal.length === 0) {
308
+ // CHECK, rodada 1 (S6): as fases nao precisam dele, mas o `ork audit run` despacha sempre pelo claude-bg.
309
+ return { nome, nivel: 'warn',
310
+ detalhe: 'binario `claude` fora do PATH (opcional para as fases: nenhum bloco dos modos permitidos despacha pelo claude-bg' +
311
+ (blocos.fallback.length
312
+ ? `; o fallback de ${blocos.fallback.length} bloco(s) cai nele e falharia: ${textoDosBlocos(blocos.fallback)}` : '') +
313
+ '; o ork audit run ainda despacha por ele)',
314
+ correcao: 'para despachar pelo claude-bg (e rodar ork audit run), instale o Claude Code e garanta `claude` no PATH' };
315
+ }
316
+ return { nome, nivel: 'fail',
317
+ detalhe: 'binario `claude` fora do PATH' + (blocos
318
+ ? `; ${blocos.principal.length} bloco(s) dos modos permitidos despacham por ele (${textoDosBlocos(blocos.principal)})` : ''),
319
+ correcao: 'instale o Claude Code e garanta `claude` no PATH; so com o Codex, passe cada bloco para ele ' +
320
+ '(ork setup <modo> --bloco N --runtime codex --model <modelo>) ou tire o modo de conduction.allowed_modes ' +
321
+ '(mantenha o conduction.default_mode entre os permitidos)' };
322
+ }
323
+ /**
324
+ * O despacho pelo Codex quando o projeto o elege: por `runtime.adapter: codex`, como antes, ou, desde
325
+ * a fatia 2 do ensaio da 0.5.0 (P1), por bloco de modo permitido. A falta do `codex`, ou um sandbox
326
+ * que nao executa, deixa de ser aviso: e exatamente o que bloquearia a fase. Sem nenhum dos dois, o
327
+ * check nao sai.
328
+ */
329
+ function checarDespachoPeloCodex(carregado, codex, sonda) {
330
+ const pelaAdapter = carregado.manifesto.runtime.adapter === 'codex';
331
+ const blocos = blocosDoRuntime(carregado, 'codex')?.principal ?? [];
332
+ if (!pelaAdapter && blocos.length === 0)
333
+ return null;
334
+ const origem = [pelaAdapter ? 'runtime.adapter: codex' : '',
335
+ blocos.length ? `${blocos.length} bloco(s) dos modos permitidos no codex (${textoDosBlocos(blocos)})` : '']
336
+ .filter(Boolean).join(' e ');
337
+ const sandboxConfigurado = carregado.manifesto.runtime.sandbox;
338
+ const sandboxQuebrado = sonda !== null && !sonda.ok && sandboxConfigurado !== 'danger-full-access';
339
+ return {
340
+ nome: 'despacho pelo codex',
341
+ nivel: !codex ? 'fail' : sandboxQuebrado ? 'fail' : 'ok',
342
+ detalhe: !codex
343
+ ? `${origem}, mas o binario \`codex\` esta fora do PATH`
344
+ : sandboxQuebrado
345
+ ? `${origem} com sandbox "${sandboxConfigurado}", mas o sandbox nao executa nesta maquina (o agente narraria sucesso sem rodar nada)`
346
+ : `${origem} com sandbox "${sandboxConfigurado}"`,
347
+ correcao: !codex
348
+ ? 'instale o Codex CLI e autentique com `codex login` (assinatura, nunca API key)'
349
+ : sandboxQuebrado
350
+ ? 'instale o bubblewrap do sistema, ou declare runtime.sandbox: danger-full-access ciente do risco'
351
+ : undefined,
352
+ };
353
+ }
144
354
  /** Roda todos os checks a partir do diretorio informado. */
145
355
  function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente_1.nomesDeProviderAtivos)()) {
146
356
  const checks = [];
@@ -160,31 +370,43 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
160
370
  });
161
371
  const repo = (0, util_1.exec)('git', ['rev-parse', '--is-inside-work-tree'], dirInicial);
162
372
  const dentroDeRepo = repo.ok && repo.stdout.trim() === 'true';
163
- const branch = (0, util_1.exec)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], dirInicial);
373
+ // Ensaio da 0.5.0: sem commit, o `rev-parse --abbrev-ref` respondia "HEAD"; a branch vem do
374
+ // `symbolic-ref`, e a falta de commit fica dita.
375
+ const semCommit = dentroDeRepo && !(0, util_1.exec)('git', ['rev-parse', '--verify', '--quiet', 'HEAD'], dirInicial).ok;
376
+ const branch = dentroDeRepo ? (0, util_1.branchDoHead)(dirInicial) ?? (0, util_1.exec)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], dirInicial).stdout.trim() : '';
164
377
  checks.push({
165
378
  nome: 'repositorio',
166
379
  nivel: dentroDeRepo ? 'ok' : 'fail',
167
- detalhe: dentroDeRepo ? `branch ${branch.stdout.trim()}` : 'fora de um repositorio git',
380
+ detalhe: dentroDeRepo ? `branch ${branch}${semCommit ? ' (sem commit)' : ''}` : 'fora de um repositorio git',
168
381
  correcao: dentroDeRepo ? undefined : 'rode o ork dentro de um repositorio git',
169
382
  });
383
+ if (dentroDeRepo) {
384
+ const dono = checarDonoDoGit(dirInicial);
385
+ if (dono)
386
+ checks.push(dono);
387
+ }
388
+ // Fatia 2 do ensaio da 0.5.0 (P1): o manifesto e lido antes dos runtimes, porque e o setup dos
389
+ // modos permitidos que diz se o `claude` e obrigatorio; a ordem das linhas do relatorio nao muda.
390
+ const carregado = (0, manifest_1.carregarManifesto)(dirInicial);
170
391
  const claude = adapter.disponivel();
171
392
  const v = claude ? adapter.versao() : null;
172
- checks.push({
173
- nome: 'runtime claude-bg',
174
- nivel: claude ? 'ok' : 'fail',
175
- detalhe: claude ? `${claude} (${v ?? 'versao desconhecida'})` : 'binario `claude` fora do PATH',
176
- correcao: claude ? undefined : 'instale o Claude Code e garanta `claude` no PATH',
177
- });
393
+ checks.push(checarRuntimeClaude(carregado, claude, v));
178
394
  // Segundo runtime homologado. Ausente e `warn`, nao `fail`: o claude-bg segue sendo o
179
395
  // padrao, e um projeto que nunca pediu codex nao pode ficar bloqueado por ele.
180
396
  const codex = codexAdapter.disponivel();
181
397
  const codexVersao = codex ? codexAdapter.versao() : null;
398
+ // CHECK, rodada 1 (S6): com o projeto despachando pelo codex, a falta dele nao e "opcional"; quem
399
+ // reprova e o check `despacho pelo codex`, mais abaixo.
400
+ const usaCodex = !!carregado && carregado.erros.length === 0 && (carregado.manifesto.runtime.adapter === 'codex' ||
401
+ (blocosDoRuntime(carregado, 'codex')?.principal.length ?? 0) > 0);
182
402
  checks.push({
183
403
  nome: 'runtime codex',
184
404
  nivel: codex ? 'ok' : 'warn',
185
405
  detalhe: codex
186
406
  ? `${codex} (${codexVersao ?? 'versao desconhecida'})`
187
- : 'binario `codex` fora do PATH (opcional: claude-bg e o runtime padrao)',
407
+ : usaCodex
408
+ ? 'binario `codex` fora do PATH (o projeto despacha por ele: veja despacho pelo codex)'
409
+ : 'binario `codex` fora do PATH (opcional: claude-bg e o runtime padrao)',
188
410
  correcao: codex
189
411
  ? undefined
190
412
  : 'para despachar pelo codex, instale o Codex CLI e autentique com `codex login`',
@@ -206,7 +428,6 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
206
428
  'nesta maquina) ou declare runtime.sandbox: danger-full-access ciente do risco',
207
429
  });
208
430
  }
209
- const carregado = (0, manifest_1.carregarManifesto)(dirInicial);
210
431
  if (!carregado) {
211
432
  checks.push({
212
433
  nome: 'manifesto',
@@ -253,26 +474,11 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
253
474
  : `${fuso.fuso} (fuso do sistema; ${horario_1.CHAVE_DO_FUSO} nao configurado)`),
254
475
  correcao: fuso.aviso ? `corrija ${horario_1.CHAVE_DO_FUSO} com um nome IANA` : undefined,
255
476
  });
256
- // Quando o manifesto ELEGE o codex como runtime de despacho, a ausencia dele (ou um
477
+ // Quando o manifesto ou um bloco de modo permitido ELEGE o codex, a ausencia dele (ou um
257
478
  // sandbox que nao executa) deixa de ser aviso: e exatamente o que bloquearia a fase.
258
- if (carregado.manifesto.runtime.adapter === 'codex') {
259
- const sandboxConfigurado = carregado.manifesto.runtime.sandbox;
260
- const sandboxQuebrado = sondaDoCodex !== null && !sondaDoCodex.ok && sandboxConfigurado !== 'danger-full-access';
261
- checks.push({
262
- nome: 'despacho pelo codex',
263
- nivel: !codex ? 'fail' : sandboxQuebrado ? 'fail' : 'ok',
264
- detalhe: !codex
265
- ? 'runtime.adapter: codex, mas o binario `codex` esta fora do PATH'
266
- : sandboxQuebrado
267
- ? `runtime.adapter: codex com sandbox "${sandboxConfigurado}", mas o sandbox nao executa nesta maquina (o agente narraria sucesso sem rodar nada)`
268
- : `runtime.adapter: codex com sandbox "${sandboxConfigurado}"`,
269
- correcao: !codex
270
- ? 'instale o Codex CLI e autentique com `codex login` (assinatura, nunca API key)'
271
- : sandboxQuebrado
272
- ? 'instale o bubblewrap do sistema, ou declare runtime.sandbox: danger-full-access ciente do risco'
273
- : undefined,
274
- });
275
- }
479
+ const despachoPeloCodex = checarDespachoPeloCodex(carregado, codex, sondaDoCodex);
480
+ if (despachoPeloCodex)
481
+ checks.push(despachoPeloCodex);
276
482
  const politica = carregado.manifesto.runtime.provider_policy;
277
483
  const soAssinatura = politica === 'subscription-only';
278
484
  const redirecionam = ENVS_QUE_REDIRECIONAM.filter(e => nomesHerdados.includes(e));
@@ -315,6 +521,7 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
315
521
  : 'files (fallback honesto: handoff por arquivos, ponteiro path#ancora)',
316
522
  correcao: estado.pedido === 'orkmind' && estado.efetivo === 'files' ? estado.correcao : undefined,
317
523
  });
524
+ checks.push(checarChaveDeEmbedding(context.loaded));
318
525
  // Bloco B3: a fila de rate limit e estado do mundo, nao detalhe interno. Uma fase
319
526
  // esperando janela e uma fase que NAO esta rodando, e quem olha o doctor precisa
320
527
  // saber disso antes de concluir que a thread travou.
@@ -352,12 +559,15 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
352
559
  checks.push(...(0, preflight_1.sondasDeAmbiente)(carregado.raiz));
353
560
  {
354
561
  const inventario = (0, sessoes_inventario_1.inventariarSessoes)(carregado.raiz, { global: true, todas: true });
562
+ // Fatia 2 do ensaio da 0.5.0 (P1): binario ausente e fonte ausente, dita no detalhe.
563
+ const ausentes = inventario.fontes.filter((f) => f.ausente).map((f) => f.origem);
355
564
  checks.push({
356
565
  nome: 'governanca de sessoes',
357
566
  nivel: inventario.ok && inventario.semThread === 0 ? 'ok' : 'warn',
358
567
  detalhe: `${inventario.escopo.usuario}, Claude e Codex, global com histórico: ${inventario.total} sessões; ` +
359
568
  `${inventario.semThread} sem thread; ${inventario.ambiguas} ambíguas; ` +
360
- `inventário ${inventario.ok ? 'válido' : 'INCOMPLETO (zero não comprovado)'}`,
569
+ `inventário ${inventario.ok ? 'válido' : 'INCOMPLETO (zero não comprovado)'}` +
570
+ (ausentes.length ? `; fonte ausente: ${ausentes.join(', ')}` : ''),
361
571
  correcao: inventario.ok && inventario.semThread === 0 ? undefined :
362
572
  'ork sessions --global --all --json; adote identidades sem vínculo com ork sessions adopt <id>',
363
573
  });