@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
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CAUDA_DO_LOG_BYTES = void 0;
37
+ exports.caudaDoLogDaRede = caudaDoLogDaRede;
38
+ exports.correcaoDaFalha = correcaoDaFalha;
39
+ exports.checarRede = checarRede;
40
+ /**
41
+ * RM-053 (fatia 2): o check "rede" do `ork doctor`.
42
+ *
43
+ * A rede pessoal publica sozinha (batida do pulse e eventos de thread) e so deixava rastro em
44
+ * `~/.orkastery/rede/rede.log`: a srvjcp86 ficou de 02/10 em diante como membro herdado da fabrica,
45
+ * com toda publicacao caindo em `rede.sem-repositorio`, e o doctor dizia PRONTO sem uma palavra.
46
+ *
47
+ * O check diz a adesao, a casa, a ultima batida publicada e a ultima falha do log. Ele so le
48
+ * arquivos locais (`rede.json`, `maquina.json`, `rede/publicada.json`, `rede/rede.log`): nao chama
49
+ * a forja, nao cria estado e roda de qualquer diretorio. Nunca reprova: a rede e opcional e o
50
+ * doctor e gate de despacho; o pior nivel e `warn`, com a correcao.
51
+ */
52
+ const fs = __importStar(require("node:fs"));
53
+ const path = __importStar(require("node:path"));
54
+ const horario_1 = require("./horario");
55
+ const rede_1 = require("./rede");
56
+ const rede_adesao_1 = require("./rede-adesao");
57
+ const rede_status_1 = require("./rede-status");
58
+ const saida_segura_1 = require("./saida-segura");
59
+ /** O fim do `rede.log` que o doctor le: o log so cresce, e o que importa e o mais recente. */
60
+ exports.CAUDA_DO_LOG_BYTES = 64 * 1024;
61
+ /** As linhas legiveis do fim do `rede.log`, da mais antiga para a mais nova. Sem log, lista vazia. */
62
+ function caudaDoLogDaRede(arquivo = path.join((0, rede_adesao_1.pastaDaRede)(), 'rede.log')) {
63
+ let texto;
64
+ try {
65
+ const fd = fs.openSync(arquivo, 'r');
66
+ try {
67
+ const tamanho = fs.fstatSync(fd).size;
68
+ const inicio = Math.max(0, tamanho - exports.CAUDA_DO_LOG_BYTES);
69
+ const buf = Buffer.alloc(tamanho - inicio);
70
+ fs.readSync(fd, buf, 0, buf.length, inicio);
71
+ texto = buf.toString('utf8');
72
+ // A primeira linha de uma cauda cortada vem pela metade.
73
+ if (inicio > 0)
74
+ texto = texto.slice(texto.indexOf('\n') + 1);
75
+ }
76
+ finally {
77
+ fs.closeSync(fd);
78
+ }
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ const registros = [];
84
+ for (const linha of texto.split('\n')) {
85
+ if (!linha.trim())
86
+ continue;
87
+ try {
88
+ const r = JSON.parse(linha);
89
+ if (typeof r.ts !== 'string' || !Number.isFinite(Date.parse(r.ts)) || typeof r.acao !== 'string')
90
+ continue;
91
+ registros.push({ ts: r.ts, acao: r.acao, origem: typeof r.origem === 'string' ? r.origem : null,
92
+ erro: typeof r.erro === 'string' ? r.erro : null });
93
+ }
94
+ catch { /* linha ilegivel: o log e informativo */ }
95
+ }
96
+ return registros;
97
+ }
98
+ const curto = (s, teto = 200) => {
99
+ const limpo = (0, saida_segura_1.emUmaLinha)(s).trim();
100
+ return limpo.length > teto ? `${limpo.slice(0, teto - 3)}...` : limpo;
101
+ };
102
+ /** A correcao pela familia do erro da ultima falha (o prefixo tipado, como `rede.sem-repositorio`). */
103
+ function correcaoDaFalha(erro) {
104
+ const codigo = /^([a-z]+\.[a-z-]+):/.exec(erro)?.[1] ?? '';
105
+ if (codigo === 'rede.sem-repositorio' || codigo === 'rede.repositorio-alheio') {
106
+ return 'a casa da rede ainda nao existe: o dono roda ork network entrar, que cria o repositorio privado; ou ork network sair, para tirar esta maquina da rede';
107
+ }
108
+ if (codigo === 'rede.sem-forja')
109
+ return 'faca login na forja (gh auth login ou glab auth login) e rode ork network publicar';
110
+ if (codigo === 'rede.repositorio-publico')
111
+ return 'torne a casa da rede privada na forja; nada e publicado num repositorio publico';
112
+ if (codigo === 'rede.nome-em-uso')
113
+ return 'escolha outro nome com ork network entrar --maquina NOME, ou tome este com --forcar';
114
+ return 'ork network status mostra as lacunas; ork network publicar tenta de novo';
115
+ }
116
+ /** O check "rede". `agoraMs` e o log ficam injetaveis para o teste. */
117
+ function checarRede(opcoes = {}) {
118
+ const nome = 'rede';
119
+ const agora = opcoes.agoraMs ?? Date.now();
120
+ const adesao = (0, rede_adesao_1.adesaoDaRede)();
121
+ if (!adesao.membro) {
122
+ return { nome, nivel: 'ok', detalhe: adesao.config && !adesao.config.membro
123
+ ? 'fora da rede (saiu com ork network sair)'
124
+ : 'fora da rede; ork network entrar poe esta maquina na rede da pessoa' };
125
+ }
126
+ const marca = (0, rede_1.lerMarcaDaRede)();
127
+ const config = adesao.config;
128
+ const casa = config?.host && config.dono && config.repositorio ? `${config.host}/${config.dono}/${config.repositorio}`
129
+ : typeof marca?.casa === 'string' ? marca.casa : null;
130
+ const registros = caudaDoLogDaRede(opcoes.log);
131
+ const falha = [...registros].reverse().find((r) => r.acao === 'falhou') ?? null;
132
+ const emBatida = marca && typeof marca.em === 'string' && Number.isFinite(Date.parse(marca.em)) ? marca.em : null;
133
+ const idade = emBatida ? agora - Date.parse(emBatida) : NaN;
134
+ const partes = [
135
+ adesao.adesao === 'fabrica' ? 'membro herdado da fabrica' : 'membro',
136
+ casa ? `casa ${curto(casa, 120)}` : 'casa ainda nao gravada',
137
+ emBatida ? `ultima batida ${(0, horario_1.formatarDataHora)(emBatida)} (ha ${(0, rede_status_1.duracao)(idade)})` : 'nenhuma batida publicada',
138
+ falha ? `ultima falha ${(0, horario_1.formatarDataHora)(falha.ts)}${falha.origem ? ` (${curto(falha.origem, 20)})` : ''}: ${curto(falha.erro ?? 'sem detalhe')}`
139
+ : 'nenhuma falha no rede.log',
140
+ ];
141
+ // A falha mais nova que a ultima batida e o estado de agora; a de antes ja passou.
142
+ const falhaAtual = !!falha && (!emBatida || Date.parse(falha.ts) > Date.parse(emBatida));
143
+ const parada = !emBatida || !(idade <= rede_status_1.SEM_BATIDA_MS);
144
+ if (!falhaAtual && !parada)
145
+ return { nome, nivel: 'ok', detalhe: partes.join('; ') };
146
+ const correcao = falhaAtual ? correcaoDaFalha(falha.erro ?? '')
147
+ : 'a batida sai do pulse: confira o cron do pulse e rode ork network publicar';
148
+ return { nome, nivel: 'warn', detalhe: partes.join('; '), correcao };
149
+ }
package/dist/doctor.js CHANGED
@@ -39,9 +39,15 @@ 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;
43
+ exports.manifestoForaDoRepositorio = manifestoForaDoRepositorio;
42
44
  exports.checarContas = checarContas;
43
45
  exports.checarChaveDeEmbedding = checarChaveDeEmbedding;
46
+ exports.checarDonoDoGit = checarDonoDoGit;
44
47
  exports.checarOnboarding = checarOnboarding;
48
+ exports.blocosDoRuntime = blocosDoRuntime;
49
+ exports.checarRuntimeClaude = checarRuntimeClaude;
50
+ exports.checarDespachoPeloCodex = checarDespachoPeloCodex;
45
51
  exports.checar = checar;
46
52
  exports.relatorio = relatorio;
47
53
  exports.doctor = doctor;
@@ -63,6 +69,42 @@ const project_state_1 = require("./project-state");
63
69
  const runtime_ambiente_1 = require("./runtime-ambiente");
64
70
  const runtime_profiles_1 = require("./runtime-profiles");
65
71
  const preflight_1 = require("./preflight");
72
+ const setup_1 = require("./setup");
73
+ const doctor_pulse_cron_1 = require("./doctor-pulse-cron");
74
+ const doctor_rede_1 = require("./doctor-rede");
75
+ /**
76
+ * Ensaio de 03/10/2026 (RM-049): o manifesto e achado subindo a partir do diretorio atual. Um
77
+ * `orkastery.yaml` de uma pasta acima do repositorio (um `ork init` rodado por engano no HOME) era
78
+ * lido como o deste projeto, com outro nome e outra abbrev, e o doctor dizia PRONTO. Vale o manifesto
79
+ * dentro do repositorio ou na arvore principal dele (a worktree de thread le o da arvore principal).
80
+ */
81
+ function manifestoForaDoRepositorio(dirInicial, caminhoDoManifesto) {
82
+ const topo = (0, util_1.exec)('git', ['rev-parse', '--show-toplevel'], dirInicial);
83
+ if (!topo.ok)
84
+ return null;
85
+ const real = (p) => { try {
86
+ return fs.realpathSync(p);
87
+ }
88
+ catch {
89
+ return path.resolve(p);
90
+ } };
91
+ const dirDoManifesto = real(path.dirname(caminhoDoManifesto));
92
+ const dentro = (raiz) => {
93
+ const r = path.relative(real(raiz), dirDoManifesto);
94
+ return r === '' || (!r.startsWith('..') && !path.isAbsolute(r));
95
+ };
96
+ if (dentro(topo.stdout.trim()))
97
+ return null;
98
+ const comum = (0, util_1.exec)('git', ['rev-parse', '--path-format=absolute', '--git-common-dir'], dirInicial);
99
+ if (comum.ok && path.basename(comum.stdout.trim()) === '.git' && dentro(path.dirname(comum.stdout.trim())))
100
+ return null;
101
+ return {
102
+ nome: 'manifesto do repositorio',
103
+ nivel: 'warn',
104
+ detalhe: `o ${manifest_1.NOME_MANIFESTO} lido fica fora deste repositorio (${caminhoDoManifesto}): o ork conduz o projeto daquela pasta, nao o de ${topo.stdout.trim()}`,
105
+ correcao: `rode ork init na raiz deste repositorio (${topo.stdout.trim()}); se o manifesto de fora nasceu por engano, apague-o junto com o AGENTS.md e o .orkastery/ ao lado dele`,
106
+ };
107
+ }
66
108
  /**
67
109
  * I-33 (D7): check "contas por runtime". Cada perfil ativo tem o login conferido pelo proprio
68
110
  * CLI com o env dele (`claude auth status`, `codex login status`); o doctor so le e relata, sem
@@ -148,6 +190,81 @@ function checarChaveDeEmbedding(carregado, env = process.env) {
148
190
  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`,
149
191
  correcao: `exporte ${variavel} com a chave dedicada ao Orkastery (com limite de credito no painel do provider)` };
150
192
  }
193
+ /** Acima disso a conferencia do dono do `.git` fica parcial (aviso), para o doctor nao pesar. */
194
+ exports.TETO_DO_DONO_DO_GIT = 200_000;
195
+ /**
196
+ * RM-037 (fatia 3, defeito 7): git rodado como root no repositorio do dono deixa objeto, pasta e
197
+ * `FETCH_HEAD` com outro dono, e o git do dono deixa de gravar neles: o fetch travou em 29/09 e em
198
+ * 01/10. O dono do repositorio e o dono do diretorio comum do git. O check acusa com a contagem, ate
199
+ * tres exemplos e o `chown` exato; o doctor nunca roda nada. Fora de repositorio git, `null`.
200
+ */
201
+ function checarDonoDoGit(dirInicial, opcoes = {}) {
202
+ const nome = 'dono do .git';
203
+ const comum = (0, util_1.exec)('git', ['rev-parse', '--git-common-dir'], dirInicial);
204
+ if (!comum.ok || !comum.stdout.trim())
205
+ return null;
206
+ const dirComum = path.resolve(dirInicial, comum.stdout.trim());
207
+ const lstat = opcoes.lstat ?? ((p) => fs.lstatSync(p));
208
+ let raiz;
209
+ try {
210
+ raiz = lstat(dirComum);
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ const teto = opcoes.teto ?? exports.TETO_DO_DONO_DO_GIT;
216
+ const relativo = (f) => path.relative(path.dirname(dirComum), f);
217
+ // A correcao sai pronta para copiar: caminho com caractere fora do conjunto seguro vai entre aspas simples.
218
+ const noShell = (s) => /^[A-Za-z0-9._/-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
219
+ const estranhos = [], uids = new Set();
220
+ let total = 0, vistos = 0, parcial = false;
221
+ const pilha = [dirComum];
222
+ while (pilha.length > 0 && !parcial) {
223
+ const atual = pilha.pop();
224
+ let nomes;
225
+ // Pasta que o dono nao consegue listar ja aparece pelo dono dela, na entrada de cima.
226
+ try {
227
+ nomes = fs.readdirSync(atual);
228
+ }
229
+ catch {
230
+ continue;
231
+ }
232
+ for (const n of nomes) {
233
+ if (++vistos > teto) {
234
+ parcial = true;
235
+ break;
236
+ }
237
+ const f = path.join(atual, n);
238
+ let st;
239
+ try {
240
+ st = lstat(f);
241
+ }
242
+ catch {
243
+ continue;
244
+ }
245
+ if (st.uid !== raiz.uid) {
246
+ total++;
247
+ uids.add(st.uid);
248
+ if (estranhos.length < 3)
249
+ estranhos.push(relativo(f));
250
+ }
251
+ if (st.isDirectory())
252
+ pilha.push(f);
253
+ }
254
+ }
255
+ if (total > 0) {
256
+ return { nome, nivel: 'fail',
257
+ detalhe: `${total} entrada(s) de ${relativo(dirComum)} com outro dono (uid ${[...uids].sort((a, b) => a - b).join(', ')}; o do ` +
258
+ `repositorio e ${raiz.uid}): ${estranhos.join(', ')}${total > estranhos.length ? ', ...' : ''}; o git do dono nao grava nelas, ` +
259
+ `e o fetch e o commit falham${parcial ? ` (conferencia parcial: mais de ${teto} entradas)` : ''}`,
260
+ correcao: `sudo chown -R ${raiz.uid}:${raiz.gid} ${noShell(dirComum)} (o ork nao roda isso sozinho)` };
261
+ }
262
+ if (parcial) {
263
+ return { nome, nivel: 'warn', detalhe: `mais de ${teto} entradas em ${relativo(dirComum)}: conferencia parcial, sem outro dono ate aqui`,
264
+ correcao: `confira o resto com: find ${noShell(dirComum)} -not -uid ${raiz.uid}` };
265
+ }
266
+ return { nome, nivel: 'ok', detalhe: `${vistos} entradas de ${relativo(dirComum)} com o dono do repositorio (uid ${raiz.uid})` };
267
+ }
151
268
  /** Checks locais; ler a entrevista não abre driver, banco, runtime ou rede. */
152
269
  function checarOnboarding(carregado) {
153
270
  const estado = (0, onboarding_1.lerOnboarding)(carregado.raiz);
@@ -163,9 +280,15 @@ function checarOnboarding(carregado) {
163
280
  checks.push({ nome: 'onboarding memoria', nivel: 'warn', detalhe: `entrevista escolheu ${modo}; manifesto declara ${carregado.manifesto.memory.mode}`,
164
281
  correcao: `revise memory.mode: ${modo} em orkastery.yaml; a entrevista não altera o manifesto` });
165
282
  }
166
- // I-35: o fuso respondido na etapa maestro orienta owner.timezone, sem editar o manifesto.
283
+ // I-35: o fuso respondido na etapa maestro orienta owner.timezone, sem editar o manifesto. Com
284
+ // `owner.timezone` na mesma resposta (gravado no manifesto pelo `onboarding set`), vale ele: no
285
+ // ensaio da 0.5.0, o `fuso` legado da resposta anterior mandava desfazer a escolha explicita.
167
286
  const maestro = estado.etapas.maestro?.conteudo;
168
- const fusoDaEntrevista = maestro && typeof maestro === 'object' && !Array.isArray(maestro) ? (0, horario_1.normalizarFuso)(maestro.fuso) : undefined;
287
+ const owner = maestro && typeof maestro === 'object' && !Array.isArray(maestro) ? maestro.owner : undefined;
288
+ const fusoDoOwner = owner && typeof owner === 'object' && !Array.isArray(owner) ? owner.timezone : undefined;
289
+ // Owner invalido (so por edicao a mao: o `onboarding set` recusa) nao cala o fuso legado.
290
+ const fusoDaEntrevista = maestro && typeof maestro === 'object' && !Array.isArray(maestro)
291
+ ? (0, horario_1.normalizarFuso)(fusoDoOwner) ?? (0, horario_1.normalizarFuso)(maestro.fuso) : undefined;
169
292
  if (fusoDaEntrevista && fusoDaEntrevista !== carregado.manifesto.owner?.timezone) {
170
293
  checks.push({ nome: 'onboarding fuso', nivel: 'warn',
171
294
  detalhe: `entrevista informou ${fusoDaEntrevista}; manifesto declara ${carregado.manifesto.owner?.timezone ?? `${horario_1.CHAVE_DO_FUSO} ausente`}`,
@@ -173,8 +296,128 @@ function checarOnboarding(carregado) {
173
296
  }
174
297
  return checks;
175
298
  }
176
- /** Roda todos os checks a partir do diretorio informado. */
177
- function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente_1.nomesDeProviderAtivos)()) {
299
+ /**
300
+ * Fatia 2 do ensaio da 0.5.0 (P1): por qual runtime cada bloco dos modos de
301
+ * `conduction.allowed_modes` despacha, pelo mesmo criterio do despacho sem opcao de CLI
302
+ * (`resolverDespacho`: o bloco do setup vence `runtime.adapter`). Quem passou os blocos para o
303
+ * Codex nao precisa do `claude`. Setup ilegivel devolve `null`: quem chama fica no lado seguro.
304
+ */
305
+ function blocosDoRuntime(carregado, runtime) {
306
+ const { manifesto } = carregado;
307
+ const r = { principal: [], fallback: [] };
308
+ try {
309
+ const setup = (0, setup_1.lerSetup)(carregado.raiz);
310
+ for (const modo of manifesto.conduction.allowed_modes) {
311
+ modos_1.MODOS[modo].blocos.forEach((b, i) => {
312
+ const doBloco = (0, setup_1.configDoBloco)(setup, modo, b.fases[0]);
313
+ const bloco = { tag: modos_1.MODOS[modo].tag, bloco: i + 1 };
314
+ if ((doBloco?.runtime ?? manifesto.runtime.adapter) === runtime)
315
+ r.principal.push(bloco);
316
+ else if (doBloco?.fallback?.some((f) => f.split(':')[0] === runtime))
317
+ r.fallback.push(bloco);
318
+ });
319
+ }
320
+ }
321
+ catch {
322
+ return null;
323
+ }
324
+ return r;
325
+ }
326
+ /** `#Classic 1, 2; #Auto 1`: os blocos agrupados por modo, na ordem dos modos permitidos. */
327
+ function textoDosBlocos(blocos) {
328
+ const porModo = new Map();
329
+ for (const b of blocos)
330
+ porModo.set(b.tag, [...(porModo.get(b.tag) ?? []), b.bloco]);
331
+ return [...porModo].map(([tag, numeros]) => `${tag} ${numeros.join(', ')}`).join('; ');
332
+ }
333
+ /**
334
+ * Ensaio de 03/10 (R1): com perfil ativo do runtime, quem confere o login e o check "contas por runtime";
335
+ * sem perfil (ou com o store ilegivel, que aquele check reprova), o despacho usa o login do `claude` do processo.
336
+ */
337
+ function temPerfilAtivo(carregado, runtime) {
338
+ if (!carregado || carregado.erros.length)
339
+ return false;
340
+ try {
341
+ return (0, runtime_profiles_1.lerPerfisComContas)(carregado.raiz).perfis.some(p => p.runtime === runtime && p.estado !== 'desativado');
342
+ }
343
+ catch {
344
+ return false;
345
+ }
346
+ }
347
+ /**
348
+ * Fatia 2 do ensaio da 0.5.0 (P1): o `claude` so e obrigatorio quando algum bloco de modo permitido
349
+ * despacha por ele. Sem manifesto, com manifesto invalido ou com setup ilegivel vale o padrao (todo
350
+ * bloco no claude-bg), e a falta reprova como antes.
351
+ */
352
+ function checarRuntimeClaude(carregado, claude, versao, auth = null) {
353
+ const nome = 'runtime claude-bg';
354
+ if (claude) {
355
+ const detalhe = `${claude} (${versao ?? 'versao desconhecida'})`;
356
+ // Ensaio de 03/10 (R1): sem perfil de conta, o despacho usa o login do proprio `claude`; sem ele, o
357
+ // primeiro despacho falha. Conferencia inconclusiva (timeout, JSON ilegivel) nao prova falta de login.
358
+ if (auth && !auth.ok && !auth.transitorio) {
359
+ return { nome, nivel: 'warn', detalhe: `${detalhe}; ${auth.detalhe}: o despacho pelo claude-bg falharia`,
360
+ correcao: auth.pago
361
+ ? 'faca o login de assinatura (claude.ai) no `claude`, sem API key nem provider de nuvem'
362
+ : 'rode `claude` uma vez, faca o login de assinatura (/login) e aceite a confianca no diretorio do projeto; ' +
363
+ 'ou crie um perfil com ork accounts add <id> --runtime claude-bg --dir <pasta>' };
364
+ }
365
+ return { nome, nivel: 'ok', detalhe };
366
+ }
367
+ const blocos = carregado && carregado.erros.length === 0 ? blocosDoRuntime(carregado, 'claude-bg') : null;
368
+ if (blocos && blocos.principal.length === 0) {
369
+ // CHECK, rodada 1 (S6): as fases nao precisam dele, mas o `ork audit run` despacha sempre pelo claude-bg.
370
+ return { nome, nivel: 'warn',
371
+ detalhe: 'binario `claude` fora do PATH (opcional para as fases: nenhum bloco dos modos permitidos despacha pelo claude-bg' +
372
+ (blocos.fallback.length
373
+ ? `; o fallback de ${blocos.fallback.length} bloco(s) cai nele e falharia: ${textoDosBlocos(blocos.fallback)}` : '') +
374
+ '; o ork audit run ainda despacha por ele)',
375
+ correcao: 'para despachar pelo claude-bg (e rodar ork audit run), instale o Claude Code e garanta `claude` no PATH' };
376
+ }
377
+ return { nome, nivel: 'fail',
378
+ detalhe: 'binario `claude` fora do PATH' + (blocos
379
+ ? `; ${blocos.principal.length} bloco(s) dos modos permitidos despacham por ele (${textoDosBlocos(blocos.principal)})` : ''),
380
+ correcao: 'instale o Claude Code e garanta `claude` no PATH; so com o Codex, passe cada bloco para ele ' +
381
+ '(ork setup <modo> --bloco N --runtime codex --model <modelo>) ou tire o modo de conduction.allowed_modes ' +
382
+ '(mantenha o conduction.default_mode entre os permitidos)' };
383
+ }
384
+ /**
385
+ * O despacho pelo Codex quando o projeto o elege: por `runtime.adapter: codex`, como antes, ou, desde
386
+ * a fatia 2 do ensaio da 0.5.0 (P1), por bloco de modo permitido. A falta do `codex`, ou um sandbox
387
+ * que nao executa, deixa de ser aviso: e exatamente o que bloquearia a fase. Sem nenhum dos dois, o
388
+ * check nao sai.
389
+ */
390
+ function checarDespachoPeloCodex(carregado, codex, sonda) {
391
+ const pelaAdapter = carregado.manifesto.runtime.adapter === 'codex';
392
+ const blocos = blocosDoRuntime(carregado, 'codex')?.principal ?? [];
393
+ if (!pelaAdapter && blocos.length === 0)
394
+ return null;
395
+ const origem = [pelaAdapter ? 'runtime.adapter: codex' : '',
396
+ blocos.length ? `${blocos.length} bloco(s) dos modos permitidos no codex (${textoDosBlocos(blocos)})` : '']
397
+ .filter(Boolean).join(' e ');
398
+ const sandboxConfigurado = carregado.manifesto.runtime.sandbox;
399
+ const sandboxQuebrado = sonda !== null && !sonda.ok && sandboxConfigurado !== 'danger-full-access';
400
+ return {
401
+ nome: 'despacho pelo codex',
402
+ nivel: !codex ? 'fail' : sandboxQuebrado ? 'fail' : 'ok',
403
+ detalhe: !codex
404
+ ? `${origem}, mas o binario \`codex\` esta fora do PATH`
405
+ : sandboxQuebrado
406
+ ? `${origem} com sandbox "${sandboxConfigurado}", mas o sandbox nao executa nesta maquina (o agente narraria sucesso sem rodar nada)`
407
+ : `${origem} com sandbox "${sandboxConfigurado}"`,
408
+ correcao: !codex
409
+ ? 'instale o Codex CLI e autentique com `codex login` (assinatura, nunca API key)'
410
+ : sandboxQuebrado
411
+ ? 'instale o bubblewrap do sistema, ou declare runtime.sandbox: danger-full-access ciente do risco'
412
+ : undefined,
413
+ };
414
+ }
415
+ /**
416
+ * Roda todos os checks a partir do diretorio informado. RM-031: `analisadores` e o check do grafo
417
+ * (`checarAnalisadoresDoGrafo` do CLI do grafo), que o `index.ts` passa: fora da familia do grafo so
418
+ * ele e o worker do MCP a abrem (fronteira do KG1), e o doctor nao a importa.
419
+ */
420
+ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente_1.nomesDeProviderAtivos)(), lerCrontab = doctor_pulse_cron_1.lerCrontabDoSistema, conferirAuthDoClaude = () => adapter.conferirAuth(null), analisadores) {
178
421
  const checks = [];
179
422
  const major = versaoNode();
180
423
  checks.push({
@@ -183,6 +426,8 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
183
426
  detalhe: `v${process.versions.node}`,
184
427
  correcao: major >= 20 ? undefined : 'instale Node 20 ou superior',
185
428
  });
429
+ if (analisadores)
430
+ checks.push(analisadores());
186
431
  const git = (0, util_1.noPath)('git');
187
432
  checks.push({
188
433
  nome: 'git',
@@ -192,31 +437,43 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
192
437
  });
193
438
  const repo = (0, util_1.exec)('git', ['rev-parse', '--is-inside-work-tree'], dirInicial);
194
439
  const dentroDeRepo = repo.ok && repo.stdout.trim() === 'true';
195
- const branch = (0, util_1.exec)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], dirInicial);
440
+ // Ensaio da 0.5.0: sem commit, o `rev-parse --abbrev-ref` respondia "HEAD"; a branch vem do
441
+ // `symbolic-ref`, e a falta de commit fica dita.
442
+ const semCommit = dentroDeRepo && !(0, util_1.exec)('git', ['rev-parse', '--verify', '--quiet', 'HEAD'], dirInicial).ok;
443
+ const branch = dentroDeRepo ? (0, util_1.branchDoHead)(dirInicial) ?? (0, util_1.exec)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], dirInicial).stdout.trim() : '';
196
444
  checks.push({
197
445
  nome: 'repositorio',
198
446
  nivel: dentroDeRepo ? 'ok' : 'fail',
199
- detalhe: dentroDeRepo ? `branch ${branch.stdout.trim()}` : 'fora de um repositorio git',
447
+ detalhe: dentroDeRepo ? `branch ${branch}${semCommit ? ' (sem commit)' : ''}` : 'fora de um repositorio git',
200
448
  correcao: dentroDeRepo ? undefined : 'rode o ork dentro de um repositorio git',
201
449
  });
450
+ if (dentroDeRepo) {
451
+ const dono = checarDonoDoGit(dirInicial);
452
+ if (dono)
453
+ checks.push(dono);
454
+ }
455
+ // Fatia 2 do ensaio da 0.5.0 (P1): o manifesto e lido antes dos runtimes, porque e o setup dos
456
+ // modos permitidos que diz se o `claude` e obrigatorio; a ordem das linhas do relatorio nao muda.
457
+ const carregado = (0, manifest_1.carregarManifesto)(dirInicial);
202
458
  const claude = adapter.disponivel();
203
459
  const v = claude ? adapter.versao() : null;
204
- checks.push({
205
- nome: 'runtime claude-bg',
206
- nivel: claude ? 'ok' : 'fail',
207
- detalhe: claude ? `${claude} (${v ?? 'versao desconhecida'})` : 'binario `claude` fora do PATH',
208
- correcao: claude ? undefined : 'instale o Claude Code e garanta `claude` no PATH',
209
- });
460
+ checks.push(checarRuntimeClaude(carregado, claude, v, claude && !temPerfilAtivo(carregado, 'claude-bg') ? conferirAuthDoClaude() : null));
210
461
  // Segundo runtime homologado. Ausente e `warn`, nao `fail`: o claude-bg segue sendo o
211
462
  // padrao, e um projeto que nunca pediu codex nao pode ficar bloqueado por ele.
212
463
  const codex = codexAdapter.disponivel();
213
464
  const codexVersao = codex ? codexAdapter.versao() : null;
465
+ // CHECK, rodada 1 (S6): com o projeto despachando pelo codex, a falta dele nao e "opcional"; quem
466
+ // reprova e o check `despacho pelo codex`, mais abaixo.
467
+ const usaCodex = !!carregado && carregado.erros.length === 0 && (carregado.manifesto.runtime.adapter === 'codex' ||
468
+ (blocosDoRuntime(carregado, 'codex')?.principal.length ?? 0) > 0);
214
469
  checks.push({
215
470
  nome: 'runtime codex',
216
471
  nivel: codex ? 'ok' : 'warn',
217
472
  detalhe: codex
218
473
  ? `${codex} (${codexVersao ?? 'versao desconhecida'})`
219
- : 'binario `codex` fora do PATH (opcional: claude-bg e o runtime padrao)',
474
+ : usaCodex
475
+ ? 'binario `codex` fora do PATH (o projeto despacha por ele: veja despacho pelo codex)'
476
+ : 'binario `codex` fora do PATH (opcional: claude-bg e o runtime padrao)',
220
477
  correcao: codex
221
478
  ? undefined
222
479
  : 'para despachar pelo codex, instale o Codex CLI e autentique com `codex login`',
@@ -238,13 +495,13 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
238
495
  'nesta maquina) ou declare runtime.sandbox: danger-full-access ciente do risco',
239
496
  });
240
497
  }
241
- const carregado = (0, manifest_1.carregarManifesto)(dirInicial);
242
498
  if (!carregado) {
243
499
  checks.push({
244
500
  nome: 'manifesto',
245
501
  nivel: 'fail',
246
502
  detalhe: `${manifest_1.NOME_MANIFESTO} nao encontrado a partir de ${dirInicial}`,
247
- correcao: 'ork init',
503
+ // Ensaio de 03/10/2026 (RM-049): fora de repositorio, o `ork init` recusa; a correcao diz onde roda-lo.
504
+ correcao: dentroDeRepo ? 'ork init' : 'entre no repositorio do projeto (ou crie um com git init e o primeiro commit) e rode ork init',
248
505
  });
249
506
  }
250
507
  else {
@@ -257,6 +514,11 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
257
514
  (carregado.avisos.length > 0 ? `; ${carregado.avisos.join('; ')}` : ''),
258
515
  correcao: carregado.erros.length > 0 ? 'corrija o manifesto e rode ork doctor de novo' : undefined,
259
516
  });
517
+ if (dentroDeRepo) {
518
+ const fora = manifestoForaDoRepositorio(dirInicial, carregado.caminho);
519
+ if (fora)
520
+ checks.push(fora);
521
+ }
260
522
  checks.push(...checarOnboarding(carregado));
261
523
  const abbrev = carregado.manifesto.project.abbrev;
262
524
  const checkAbbrev = (0, slug_1.validarAbbrev)(abbrev);
@@ -285,26 +547,11 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
285
547
  : `${fuso.fuso} (fuso do sistema; ${horario_1.CHAVE_DO_FUSO} nao configurado)`),
286
548
  correcao: fuso.aviso ? `corrija ${horario_1.CHAVE_DO_FUSO} com um nome IANA` : undefined,
287
549
  });
288
- // Quando o manifesto ELEGE o codex como runtime de despacho, a ausencia dele (ou um
550
+ // Quando o manifesto ou um bloco de modo permitido ELEGE o codex, a ausencia dele (ou um
289
551
  // sandbox que nao executa) deixa de ser aviso: e exatamente o que bloquearia a fase.
290
- if (carregado.manifesto.runtime.adapter === 'codex') {
291
- const sandboxConfigurado = carregado.manifesto.runtime.sandbox;
292
- const sandboxQuebrado = sondaDoCodex !== null && !sondaDoCodex.ok && sandboxConfigurado !== 'danger-full-access';
293
- checks.push({
294
- nome: 'despacho pelo codex',
295
- nivel: !codex ? 'fail' : sandboxQuebrado ? 'fail' : 'ok',
296
- detalhe: !codex
297
- ? 'runtime.adapter: codex, mas o binario `codex` esta fora do PATH'
298
- : sandboxQuebrado
299
- ? `runtime.adapter: codex com sandbox "${sandboxConfigurado}", mas o sandbox nao executa nesta maquina (o agente narraria sucesso sem rodar nada)`
300
- : `runtime.adapter: codex com sandbox "${sandboxConfigurado}"`,
301
- correcao: !codex
302
- ? 'instale o Codex CLI e autentique com `codex login` (assinatura, nunca API key)'
303
- : sandboxQuebrado
304
- ? 'instale o bubblewrap do sistema, ou declare runtime.sandbox: danger-full-access ciente do risco'
305
- : undefined,
306
- });
307
- }
552
+ const despachoPeloCodex = checarDespachoPeloCodex(carregado, codex, sondaDoCodex);
553
+ if (despachoPeloCodex)
554
+ checks.push(despachoPeloCodex);
308
555
  const politica = carregado.manifesto.runtime.provider_policy;
309
556
  const soAssinatura = politica === 'subscription-only';
310
557
  const redirecionam = ENVS_QUE_REDIRECIONAM.filter(e => nomesHerdados.includes(e));
@@ -380,22 +627,32 @@ function checar(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente
380
627
  : `${dirEstadoProjeto} ausente`,
381
628
  correcao: fs.existsSync(dirEstadoProjeto) ? undefined : 'ork init cria o diretorio de estado',
382
629
  });
630
+ // RM-039 (B6): com o transporte do pulse configurado, a varredura tem de bater de 15 em 15
631
+ // minutos; a instalacao antiga em `0 * * * *` vira aviso com a linha nova. Nunca edita o crontab.
632
+ const cronDoPulse = (0, doctor_pulse_cron_1.checarCronDoPulse)(path.join(dirEstadoProjeto, 'monitor'), (0, estado_thread_1.raizDoEstado)(carregado.raiz), lerCrontab);
633
+ if (cronDoPulse)
634
+ checks.push(cronDoPulse);
383
635
  checks.push(checarContas(carregado.raiz));
384
636
  // I-33 (D7): umask e permissoes do estado, na mesma regra do sensor (sem bits 0o022).
385
637
  checks.push(...(0, preflight_1.sondasDeAmbiente)(carregado.raiz));
386
638
  {
387
639
  const inventario = (0, sessoes_inventario_1.inventariarSessoes)(carregado.raiz, { global: true, todas: true });
640
+ // Fatia 2 do ensaio da 0.5.0 (P1): binario ausente e fonte ausente, dita no detalhe.
641
+ const ausentes = inventario.fontes.filter((f) => f.ausente).map((f) => f.origem);
388
642
  checks.push({
389
643
  nome: 'governanca de sessoes',
390
644
  nivel: inventario.ok && inventario.semThread === 0 ? 'ok' : 'warn',
391
645
  detalhe: `${inventario.escopo.usuario}, Claude e Codex, global com histórico: ${inventario.total} sessões; ` +
392
646
  `${inventario.semThread} sem thread; ${inventario.ambiguas} ambíguas; ` +
393
- `inventário ${inventario.ok ? 'válido' : 'INCOMPLETO (zero não comprovado)'}`,
647
+ `inventário ${inventario.ok ? 'válido' : 'INCOMPLETO (zero não comprovado)'}` +
648
+ (ausentes.length ? `; fonte ausente: ${ausentes.join(', ')}` : ''),
394
649
  correcao: inventario.ok && inventario.semThread === 0 ? undefined :
395
650
  'ork sessions --global --all --json; adote identidades sem vínculo com ork sessions adopt <id>',
396
651
  });
397
652
  }
398
653
  }
654
+ // RM-053 (fatia 2): a rede da pessoa e da maquina, nao do projeto; vale de qualquer diretorio.
655
+ checks.push((0, doctor_rede_1.checarRede)());
399
656
  return checks;
400
657
  }
401
658
  /** Texto do relatorio, com o resumo "o que vale nesta maquina agora". */
@@ -431,8 +688,8 @@ function relatorio(checks) {
431
688
  return linhas.join('\n');
432
689
  }
433
690
  /** Executa o doctor e devolve o codigo de saida (0 = pronto). */
434
- function doctor(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente_1.nomesDeProviderAtivos)()) {
435
- const checks = checar(dirInicial, nomesHerdados);
691
+ function doctor(dirInicial = process.cwd(), nomesHerdados = (0, runtime_ambiente_1.nomesDeProviderAtivos)(), analisadores) {
692
+ const checks = checar(dirInicial, nomesHerdados, undefined, undefined, analisadores);
436
693
  const falhas = checks.filter((c) => c.nivel === 'fail').length;
437
694
  return { texto: relatorio(checks), codigo: falhas > 0 ? 1 : 0 };
438
695
  }