@orkastery/cli 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/agents/ork-check.md +1 -1
  4. package/adapters/claude-code/agents/ork-go.md +2 -2
  5. package/adapters/claude-code/agents/ork-goal.md +1 -1
  6. package/adapters/claude-code/agents/ork-master.md +1 -1
  7. package/adapters/claude-code/agents/ork-plan.md +1 -1
  8. package/adapters/claude-code/agents/ork-ship.md +1 -1
  9. package/adapters/claude-code/commands/master.md +11 -8
  10. package/adapters/claude-code/commands/ork.md +8 -0
  11. package/adapters/claude-code/commands/ship.md +2 -2
  12. package/adapters/codex/skills/ork/SKILL.md +12 -1
  13. package/adapters/hermes/README.md +3 -0
  14. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  15. package/adapters/hermes/bin/ork-brain.sh +3 -0
  16. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  17. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  18. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  19. package/adapters/hermes/hermes.plugin.json +2 -0
  20. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  21. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  22. package/adapters/openclaw/README.md +34 -2
  23. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  24. package/adapters/openclaw/dist/index.js +92 -20
  25. package/adapters/openclaw/openclaw.plugin.json +10 -1
  26. package/adapters/openclaw/package.json +2 -2
  27. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  28. package/adapters/openclaw/src/index.ts +108 -20
  29. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  30. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  31. package/assets/docs/produto/_modelo-feature.md +2 -0
  32. package/assets/orkmind_bridge.py +214 -1
  33. package/dist/adapters/claude-bg.js +6 -3
  34. package/dist/board.js +74 -0
  35. package/dist/branch-de-estado.js +5 -4
  36. package/dist/busca-semantica.js +161 -0
  37. package/dist/ci.js +141 -10
  38. package/dist/company-brain-cli.js +27 -2
  39. package/dist/company-brain-context.js +132 -0
  40. package/dist/company-brain-dossie.js +376 -0
  41. package/dist/company-brain-mcp.js +4 -1
  42. package/dist/conducao.js +71 -18
  43. package/dist/decisao-autonoma.js +42 -0
  44. package/dist/docs.js +64 -22
  45. package/dist/doctor.js +33 -0
  46. package/dist/entrega-pr.js +109 -5
  47. package/dist/experiencia-instalacao.js +256 -0
  48. package/dist/experiencia.js +72 -0
  49. package/dist/fabrica-estado.js +43 -10
  50. package/dist/fechamento.js +105 -0
  51. package/dist/forja.js +435 -0
  52. package/dist/gates.js +9 -1
  53. package/dist/hitl-classificacao.js +11 -1
  54. package/dist/hitl-contract.js +50 -7
  55. package/dist/hitl-curto.js +158 -0
  56. package/dist/hitl-gates.js +67 -1
  57. package/dist/hitl-lote.js +30 -15
  58. package/dist/hitl-presentation.js +10 -7
  59. package/dist/hitl-resumo.js +33 -12
  60. package/dist/hitl-texto-livre.js +150 -0
  61. package/dist/hosts.js +86 -1
  62. package/dist/index.js +602 -38
  63. package/dist/indice-vetorial.js +317 -0
  64. package/dist/init.js +13 -0
  65. package/dist/integracoes-locais.js +1 -1
  66. package/dist/intelligence-benchmark-contract.js +570 -0
  67. package/dist/intelligence-graph-cli.js +430 -0
  68. package/dist/intelligence-graph-contract.js +577 -0
  69. package/dist/intelligence-graph-extract-md.js +469 -0
  70. package/dist/intelligence-graph-extract-ts.js +810 -0
  71. package/dist/intelligence-graph-extract.js +369 -0
  72. package/dist/intelligence-graph-index.js +512 -0
  73. package/dist/intelligence-graph-parsers.js +327 -0
  74. package/dist/intelligence-graph-query.js +344 -0
  75. package/dist/intelligence-graph-repo.js +187 -0
  76. package/dist/leases.js +84 -0
  77. package/dist/ledger.js +14 -0
  78. package/dist/maestro-cli.js +10 -1
  79. package/dist/maestro-contract.js +5 -1
  80. package/dist/maestro-discovery.js +16 -2
  81. package/dist/maestro-snapshot.js +6 -2
  82. package/dist/manifest.js +142 -5
  83. package/dist/master-batch.js +3 -2
  84. package/dist/master-digest.js +2 -2
  85. package/dist/master-nota.js +203 -0
  86. package/dist/master.js +4 -0
  87. package/dist/mcp-experiencia.js +164 -0
  88. package/dist/mcp-git.js +4 -0
  89. package/dist/mcp-install.js +3 -1
  90. package/dist/mcp-maestro.js +3 -2
  91. package/dist/mcp-server.js +84 -5
  92. package/dist/memoria.js +147 -1
  93. package/dist/network-roadmap.js +907 -0
  94. package/dist/ocupacao.js +38 -2
  95. package/dist/onboarding.js +113 -7
  96. package/dist/orkmind.js +165 -9
  97. package/dist/phase.js +166 -13
  98. package/dist/policies.js +2 -0
  99. package/dist/projeto-alvo.js +551 -0
  100. package/dist/prompts.js +1 -1
  101. package/dist/pulse-consentimento.js +4 -2
  102. package/dist/pulse-delivery.js +5 -0
  103. package/dist/pulse-resposta.js +674 -49
  104. package/dist/pulse.js +21 -0
  105. package/dist/retry.js +51 -8
  106. package/dist/roadmap-reservas.js +262 -20
  107. package/dist/roadmap-status.js +213 -0
  108. package/dist/thread-close.js +3 -0
  109. package/dist/verify.js +4 -3
  110. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  111. package/eval/casos/orchestration-experience.json +48 -0
  112. package/package.json +1 -1
  113. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  114. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  115. package/schemas/maestro-snapshot.schema.json +23 -0
  116. package/skills/README.md +7 -5
  117. package/skills/core/onboarding/SKILL.md +7 -0
  118. package/skills/core/orchestration-experience/SKILL.md +113 -0
  119. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  120. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  121. package/skills/phases/go-implementation/SKILL.md +2 -0
@@ -0,0 +1,187 @@
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.TENANT_PADRAO = void 0;
37
+ exports.identidadeDaLeitura = identidadeDaLeitura;
38
+ exports.revisaoDaArvore = revisaoDaArvore;
39
+ exports.headsDasArvores = headsDasArvores;
40
+ exports.lerRepositorio = lerRepositorio;
41
+ /**
42
+ * RM-031 KG2 (D8): leitura do repositorio Git local para o extrator. E a unica borda de E/S do
43
+ * KG2: lista os arquivos rastreados, le os bytes da arvore de trabalho e fixa a revisao. Nao
44
+ * executa shell nem texto vindo do repositorio; o Git roda com argumentos fixos. O KG3 usa daqui a
45
+ * identidade da leitura e a revisao da arvore limpa, sem ler os arquivos.
46
+ *
47
+ * Fica fora e declarado: link simbolico, submodulo, arquivo em conflito, arquivo rastreado que
48
+ * sumiu da arvore, arquivo cujo caminho real sai da raiz e caminho que nao e UTF-8. A revisao so e
49
+ * o HEAD quando nada rastreado mudou e os bytes lidos sao os blobs do indice (sem filtro do Git).
50
+ */
51
+ const node_crypto_1 = require("node:crypto");
52
+ const fs = __importStar(require("node:fs"));
53
+ const path = __importStar(require("node:path"));
54
+ const node_child_process_1 = require("node:child_process");
55
+ const yaml_1 = require("./yaml");
56
+ exports.TENANT_PADRAO = 'local';
57
+ const ID_DE_REPOSITORIO = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
58
+ /** Git com argumentos fixos; o fsmonitor do repositorio nao roda (seria comando externo configurado nele). */
59
+ function git(raiz, args) {
60
+ const r = (0, node_child_process_1.spawnSync)('git', ['-c', 'core.fsmonitor=false', ...args], {
61
+ cwd: raiz, maxBuffer: 512 * 1024 * 1024, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
62
+ });
63
+ return { ok: r.status === 0, saida: r.stdout ?? Buffer.alloc(0) };
64
+ }
65
+ function obrigatorio(raiz, args) {
66
+ const r = git(raiz, args);
67
+ if (!r.ok)
68
+ throw new Error(`extracao.repositorio.git-falhou em git ${args[0]}`);
69
+ return r.saida;
70
+ }
71
+ const utf8 = (b) => {
72
+ try {
73
+ return new TextDecoder('utf-8', { fatal: true }).decode(b);
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ };
79
+ /** Nome do projeto no `orkastery.yaml`, quando ha um valido na raiz. */
80
+ function nomeDoProjeto(raiz) {
81
+ const arquivo = path.join(raiz, 'orkastery.yaml');
82
+ if (!fs.existsSync(arquivo))
83
+ return null;
84
+ try {
85
+ const dados = (0, yaml_1.lerYaml)(fs.readFileSync(arquivo, 'utf8'));
86
+ const projeto = dados && typeof dados === 'object' && !Array.isArray(dados) ? dados.project : null;
87
+ const nome = projeto && typeof projeto === 'object' && !Array.isArray(projeto) ? projeto.name : null;
88
+ return typeof nome === 'string' && ID_DE_REPOSITORIO.test(nome) ? nome : null;
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ /** Repositorio, tenant e ACL da leitura: os informados, ou os padroes (D8). */
95
+ function identidadeDaLeitura(raiz, opcoes = {}) {
96
+ const repositorio = opcoes.repository_id ?? nomeDoProjeto(raiz);
97
+ if (!repositorio || !ID_DE_REPOSITORIO.test(repositorio))
98
+ throw new Error('extracao.repositorio.sem-id');
99
+ return { repository_id: repositorio, tenant_id: opcoes.tenant_id ?? exports.TENANT_PADRAO, acl_refs: [...(opcoes.acl_refs ?? [`repo:${repositorio}:leitura`])] };
100
+ }
101
+ /**
102
+ * RM-031 KG3 (D3): o HEAD e se a arvore esta limpa nos rastreados, sem ler arquivo nenhum. O filtro
103
+ * do Git (eol, LFS) so aparece lendo os bytes: quem indexa confere a revisao do `lerRepositorio`.
104
+ */
105
+ function revisaoDaArvore(diretorio) {
106
+ const raiz = obrigatorio(diretorio, ['rev-parse', '--show-toplevel']).toString('utf8').trim();
107
+ const head = git(raiz, ['rev-parse', '--verify', '-q', 'HEAD']);
108
+ if (!head.ok)
109
+ return { raiz, head: null, motivo: 'sem-commit' };
110
+ const sujo = obrigatorio(raiz, ['status', '--porcelain=v1', '-z', '--untracked-files=no']).length > 0;
111
+ return { raiz, head: head.saida.toString('utf8').trim(), motivo: sujo ? 'working-tree-modified' : null };
112
+ }
113
+ /**
114
+ * RM-031 KG3 (CHECK rodada 1, A6): o HEAD de cada arvore do repositorio (a principal e as worktrees),
115
+ * pelo `git worktree list --porcelain`. O estado do indice e compartilhado entre elas.
116
+ */
117
+ function headsDasArvores(diretorio) {
118
+ const saida = obrigatorio(diretorio, ['worktree', 'list', '--porcelain']).toString('utf8');
119
+ return [...new Set(saida.split('\n').filter((l) => /^HEAD [0-9a-f]{40,64}$/.test(l)).map((l) => l.slice(5)))].sort();
120
+ }
121
+ /** Le o repositorio que contem `diretorio` e devolve a entrada do extrator. */
122
+ function lerRepositorio(diretorio, opcoes = {}) {
123
+ const raiz = obrigatorio(diretorio, ['rev-parse', '--show-toplevel']).toString('utf8').trim();
124
+ const { repository_id: repositorio, tenant_id, acl_refs } = identidadeDaLeitura(raiz, opcoes);
125
+ // `-s` traz modo e estagio: 120000 e link simbolico, 160000 e submodulo, estagio > 0 e conflito.
126
+ const registros = obrigatorio(raiz, ['ls-files', '-z', '-s', '--full-name']);
127
+ const porCaminho = new Map(), excluidas = [];
128
+ let inicio = 0;
129
+ for (let i = 0; i < registros.length; i++) {
130
+ if (registros[i] !== 0)
131
+ continue;
132
+ const registro = registros.subarray(inicio, i);
133
+ inicio = i + 1;
134
+ const tab = registro.indexOf(0x09);
135
+ if (tab < 0)
136
+ continue;
137
+ const [modo, objeto, estagio] = registro.subarray(0, tab).toString('latin1').split(' ');
138
+ const caminho = utf8(registro.subarray(tab + 1));
139
+ if (caminho === null) {
140
+ excluidas.push({ path: registro.subarray(tab + 1).toString('latin1'), motivo: 'caminho-nao-utf8' });
141
+ continue;
142
+ }
143
+ const atual = porCaminho.get(caminho);
144
+ porCaminho.set(caminho, { modo, objeto, estagios: (atual?.estagios ?? 0) + (estagio === '0' ? 0 : 1) });
145
+ }
146
+ const fontes = [], raizReal = fs.realpathSync(raiz);
147
+ let filtrado = false;
148
+ for (const [caminho, { modo, objeto, estagios }] of porCaminho) {
149
+ if (estagios > 0)
150
+ excluidas.push({ path: caminho, motivo: 'conflito-de-merge' });
151
+ else if (modo === '120000')
152
+ excluidas.push({ path: caminho, motivo: 'link-simbolico' });
153
+ else if (modo === '160000')
154
+ excluidas.push({ path: caminho, motivo: 'submodulo' });
155
+ else {
156
+ const absoluto = path.join(raiz, caminho);
157
+ const st = fs.lstatSync(absoluto, { throwIfNoEntry: false });
158
+ if (!st)
159
+ excluidas.push({ path: caminho, motivo: 'ausente-na-arvore' });
160
+ else if (!st.isFile())
161
+ excluidas.push({ path: caminho, motivo: 'nao-e-arquivo' });
162
+ // Pasta do caminho trocada por link simbolico levaria a leitura para fora do repositorio.
163
+ else if (!fs.realpathSync(absoluto).startsWith(raizReal + path.sep))
164
+ excluidas.push({ path: caminho, motivo: 'fora-do-repositorio' });
165
+ else {
166
+ const bytes = fs.readFileSync(absoluto);
167
+ if ((0, node_crypto_1.createHash)('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex') !== objeto)
168
+ filtrado = true;
169
+ fontes.push({ path: caminho, bytes });
170
+ }
171
+ }
172
+ }
173
+ const head = git(raiz, ['rev-parse', '--verify', '-q', 'HEAD']);
174
+ const sujo = obrigatorio(raiz, ['status', '--porcelain=v1', '-z', '--untracked-files=no']).length > 0;
175
+ const revisao = head.ok ? head.saida.toString('utf8').trim() : null;
176
+ // A5: filtro do Git (eol, LFS) deixa o status limpo com bytes que nao sao o blob da revisao.
177
+ const motivo = !revisao ? 'sem-commit' : sujo ? 'working-tree-modified' : filtrado ? 'filtro-do-git' : null;
178
+ return {
179
+ tenant_id,
180
+ repository_id: repositorio,
181
+ revision: motivo === null ? revisao : null,
182
+ revision_unavailable_reason: motivo,
183
+ acl_refs,
184
+ fontes,
185
+ excluidas,
186
+ };
187
+ }
package/dist/leases.js CHANGED
@@ -76,6 +76,8 @@ exports.posicaoNaFila = posicaoNaFila;
76
76
  exports.proximoDaFila = proximoDaFila;
77
77
  exports.adquirirRegiao = adquirirRegiao;
78
78
  exports.tabelaDeLeases = tabelaDeLeases;
79
+ exports.soltarDaThread = soltarDaThread;
80
+ exports.soltarThreadFechada = soltarThreadFechada;
79
81
  const fs = __importStar(require("node:fs"));
80
82
  const path = __importStar(require("node:path"));
81
83
  const manifest_1 = require("./manifest");
@@ -83,6 +85,8 @@ const estado_thread_1 = require("./estado-thread");
83
85
  const util_1 = require("./util");
84
86
  const horario_1 = require("./horario");
85
87
  const conducao_texto_1 = require("./conducao-texto");
88
+ const ledger_1 = require("./ledger");
89
+ const thread_1 = require("./thread");
86
90
  /** Nome do lease que serializa o merge na base (visao, paridade 5). */
87
91
  exports.LEASE_MAIN_TREE = 'main-tree';
88
92
  /** TTL padrao de 30 minutos: um SHIP que passe disso deixou de existir. */
@@ -433,6 +437,12 @@ function proximoDaFila(raiz, nome) {
433
437
  function adquirirRegiao(raiz, nome, opcoes) {
434
438
  const tipo = tipoDoLease(nome);
435
439
  const base = { nome, tipo, lease: null, ocupadoPor: null, tomadoDeVencido: false };
440
+ // A poda e limpeza de estado alheio: erro de E/S nela nunca impede a thread viva de pedir a regiao
441
+ // (achado A1 do CHECK 1); o que nao saiu agora sai no proximo pedido.
442
+ try {
443
+ podarThreadsFechadas(raiz, nome, opcoes.thread);
444
+ }
445
+ catch { /* a regra de colisao abaixo decide */ }
436
446
  const colidentes = leasesColidentes(raiz, nome, opcoes.thread);
437
447
  if (colidentes.length > 0) {
438
448
  const dono = colidentes[0];
@@ -550,3 +560,77 @@ function tabelaDeLeases(raiz) {
550
560
  linhas.push((0, horario_1.legendaDoFuso)());
551
561
  return linhas.join('\n');
552
562
  }
563
+ // ---------------------------------------------------------------------------
564
+ // RM-037 (rm037noite, defeito 2): thread fechada nao segura regiao nem fila.
565
+ // ---------------------------------------------------------------------------
566
+ /**
567
+ * Thread fechada nesta maquina? So `status: fechada` conta: thread que nao existe aqui (outro
568
+ * perfil, estado apagado) segue contando, porque ninguem prova que ela acabou.
569
+ */
570
+ function threadFechada(raiz, thread) {
571
+ try {
572
+ return (0, thread_1.lerThread)(raiz, thread).status === 'fechada';
573
+ }
574
+ catch {
575
+ return false;
576
+ }
577
+ }
578
+ /**
579
+ * Tira da thread os leases de escrita e as entradas dela na fila. O `exec:` fica de fora: a
580
+ * conducao tem ciclo proprio e ja trata thread fechada como sem conducao.
581
+ */
582
+ function soltarDaThread(raiz, thread) {
583
+ const leases = [];
584
+ for (const lease of listarLeases(raiz)) {
585
+ if (lease.thread !== thread || tipoDoLease(lease.nome) === 'exec')
586
+ continue;
587
+ // Achado A2 do CHECK 1: rele logo antes de apagar. Outra poda pode ter soltado este lease e uma
588
+ // thread viva pode ter adquirido o mesmo nome no meio; apagar pelo caminho da listagem levaria o
589
+ // lease dela. So sai o arquivo que ainda e o da thread fechada, com o mesmo carimbo.
590
+ const atual = lerLease(raiz, lease.nome);
591
+ if (!atual || atual.thread !== thread || atual.adquiridoEm !== lease.adquiridoEm)
592
+ continue;
593
+ try {
594
+ fs.unlinkSync(caminhoLease(raiz, lease.nome));
595
+ leases.push(lease.nome);
596
+ }
597
+ catch { /* outra limpeza chegou antes */ }
598
+ }
599
+ const fila = lerFila(raiz);
600
+ const dela = fila.filter((p) => p.thread === thread);
601
+ if (dela.length > 0)
602
+ gravarFila(raiz, fila.filter((p) => p.thread !== thread));
603
+ return { leases, fila: dela.map((p) => p.nome) };
604
+ }
605
+ /**
606
+ * Solta o que a thread fechada deixou e registra no ledger dela: `lease_released` por lease e
607
+ * `lease_dequeued` por entrada de fila, com a origem (o fechamento ou a poda de quem pediu a regiao).
608
+ */
609
+ function soltarThreadFechada(raiz, thread, origem, pedidaPor) {
610
+ const solto = soltarDaThread(raiz, thread);
611
+ const dir = (0, thread_1.dirThread)(raiz, thread);
612
+ const quem = pedidaPor ? { pedidaPor } : {};
613
+ for (const lease of solto.leases) {
614
+ (0, ledger_1.registrarSeExiste)(dir, thread, ledger_1.TIPOS_DE_EVENTO.leaseLiberado, { lease, ok: true, detalhe: 'thread fechada: lease solto', origem, ...quem });
615
+ }
616
+ for (const lease of solto.fila) {
617
+ (0, ledger_1.registrarSeExiste)(dir, thread, ledger_1.TIPOS_DE_EVENTO.leaseDesenfileirado, { lease, detalhe: 'thread fechada: saiu da fila', origem, ...quem });
618
+ }
619
+ return solto;
620
+ }
621
+ /**
622
+ * Antes de decidir a vez, quem pede a regiao tira da frente o que e de thread ja fechada: o lease
623
+ * colidente e a espera na fila. E a cura do que ficou preso antes de o fechamento soltar sozinho
624
+ * (a ork-companybrai3, fechada, na frente da fila de `path:docs/roadmap/README.md`).
625
+ */
626
+ function podarThreadsFechadas(raiz, nome, quem) {
627
+ const suspeitas = new Set([
628
+ ...listarLeases(raiz).filter((l) => leasesColidem(nome, l.nome)).map((l) => l.thread),
629
+ ...esperandoPor(raiz, nome).map((p) => p.thread),
630
+ ]);
631
+ suspeitas.delete(quem);
632
+ for (const thread of suspeitas) {
633
+ if (threadFechada(raiz, thread))
634
+ soltarThreadFechada(raiz, thread, 'poda', quem);
635
+ }
636
+ }
package/dist/ledger.js CHANGED
@@ -99,8 +99,18 @@ exports.TIPOS_DE_EVENTO = {
99
99
  worktreeAuditada: 'worktree_audited',
100
100
  worktreeLiberada: 'worktree_released',
101
101
  leaseEnfileirado: 'lease_queued',
102
+ // RM-037 (rm037noite, defeito 2): a entrada da fila que saiu porque a thread fechou.
103
+ leaseDesenfileirado: 'lease_dequeued',
104
+ // RM-037 (rm037noite, defeito 3): a reserva do roadmap que a thread fechada soltou (ou reapontou),
105
+ // e a que ficou para depois porque nao deu para soltar agora.
106
+ reservaLiberada: 'roadmap_reserva_liberada',
107
+ reservaPendente: 'roadmap_reserva_pendente',
108
+ // A parte do fechamento que falhou (E/S), com o erro; o fechamento segue valendo.
109
+ solturaFalhou: 'soltura_ao_fechar_falhou',
102
110
  postmortemGravado: 'postmortem_recorded',
103
111
  masterConcluido: 'master_done',
112
+ // RM-048 (item 8): o pedido de nota ao dono, com codigo curto; a nota volta pelo ingresso.
113
+ notaPedida: 'master_nota_pedida',
104
114
  scoreProposto: 'score_proposto',
105
115
  masterMigrado: 'master_migrated',
106
116
  threadFechadaAdmin: 'thread_closed_admin',
@@ -138,6 +148,8 @@ exports.TIPOS_DE_EVENTO = {
138
148
  onboardingRegistrado: 'onboarding_recorded',
139
149
  // Correcao do escalonador: vaga devolvida por thread sem procura ativa (`ork board reap`)
140
150
  vagaLiberada: 'slot_released',
151
+ // RM-037 (rm037defeito, defeito 3): o `phase run` recusou o despacho por falta de vaga no projeto.
152
+ vagaRecusada: 'slot_refused',
141
153
  // I-07: estimativa humana no PLAN para comparacao economica posterior.
142
154
  estimativaPlano: 'plan_estimate',
143
155
  // I-42 (D5): ciclo sem CHECK que nao achou teste nem comando honesto declara a ausencia de
@@ -156,6 +168,8 @@ exports.TIPOS_DE_EVENTO = {
156
168
  conducaoLiberada: 'conducao_liberada',
157
169
  conducaoOrfaLiberada: 'conducao_orfa_liberada',
158
170
  conducaoAssumida: 'conducao_assumida',
171
+ // RM-037 (rm037defeito, A-1 do CHECK 3): o despacho recusado antes da sessao devolveu o lease que a tomada consumiu.
172
+ conducaoDevolvida: 'conducao_devolvida',
159
173
  conducaoRenovada: 'conducao_renovada',
160
174
  };
161
175
  /**
@@ -31,10 +31,19 @@ function readMaestro(context, options) {
31
31
  return sources;
32
32
  } });
33
33
  }
34
+ /** RM-052: de onde veio o projeto do panorama, na lingua do dono. */
35
+ const ORIGEM_DO_PANORAMA = Object.freeze({
36
+ installation: 'fixado na instalação', cwd: 'pelo diretório atual', worktree: 'pela worktree da thread',
37
+ selection: 'pedido explicitamente (--projeto ou ORK_PROJETO)',
38
+ });
34
39
  function maestroText(snapshot) {
35
- return [`${snapshot.project.name} · panorama Maestro`,
40
+ const p = snapshot.project;
41
+ return [`${p.name} · panorama Maestro`,
36
42
  // I-35: o snapshot guarda ISO; o texto ao dono diz quando consultou, no fuso dele.
37
43
  `• Consulta: ${(0, horario_1.formatarDataHoraRotulada)(snapshot.observedAt, { agora: snapshot.observedAt })}`,
44
+ // RM-052: qual cópia foi lida e o que o panorama não lê, antes de qualquer seção.
45
+ `• Projeto consultado: ${p.name}${p.root ? ` · ${p.root}` : ''}${p.remote === undefined ? '' : ` · ${p.remote ?? 'sem remoto'}`} · ${ORIGEM_DO_PANORAMA[p.origin]}`,
46
+ ...(snapshot.notConsulted?.length ? [`• Não lido: ${snapshot.notConsulted.join(' · ')}`] : []),
38
47
  ...maestro_contract_1.SECTION_NAMES.map(name => {
39
48
  const s = snapshot.sections[name];
40
49
  return `• ${name}: ${s.state} · ${s.coverage.returned}/${s.coverage.total ?? '?'}${s.coverage.omitted ? ' · mais itens disponíveis' : ''}`;
@@ -31,10 +31,14 @@ exports.maestroSectionSchema = zod_1.z.object({
31
31
  }).strict();
32
32
  exports.maestroSnapshotSchema = zod_1.z.object({
33
33
  schema: zod_1.z.literal(exports.MAESTRO_SCHEMA), observedAt: zod_1.z.string().datetime(), fingerprint,
34
- project: zod_1.z.object({ id: identity, name: text, origin: zod_1.z.enum(['installation', 'cwd', 'worktree', 'selection']), fingerprint }).strict(),
34
+ // RM-052: a raiz exibida (com ~) e o remoto sem credencial dizem QUAL copia foi lida.
35
+ project: zod_1.z.object({ id: identity, name: text, origin: zod_1.z.enum(['installation', 'cwd', 'worktree', 'selection']), fingerprint,
36
+ root: text.optional(), remote: text.nullable().optional() }).strict(),
35
37
  limits: zod_1.z.object({ items: zod_1.z.literal(50), bytes: zod_1.z.literal(65536), sourceMs: zod_1.z.literal(2000), totalMs: zod_1.z.literal(10000) }).strict(),
36
38
  sections: zod_1.z.object(Object.fromEntries(exports.SECTION_NAMES.map(name => [name, exports.maestroSectionSchema]))).strict(),
37
39
  gaps: zod_1.z.array(text).max(100), conflicts: zod_1.z.array(text).max(100),
40
+ // RM-052: o que o panorama NAO le (roadmap, reservas, outras maquinas): lacuna declarada nunca vira "vazio".
41
+ notConsulted: zod_1.z.array(text).max(20).optional(),
38
42
  }).strict();
39
43
  function validateMaestroSnapshot(input) {
40
44
  const value = exports.maestroSnapshotSchema.parse(input);
@@ -42,6 +42,20 @@ const node_crypto_1 = require("node:crypto");
42
42
  const manifest_1 = require("./manifest");
43
43
  const estado_thread_1 = require("./estado-thread");
44
44
  const thread_1 = require("./thread");
45
+ const projeto_alvo_1 = require("./projeto-alvo");
46
+ /**
47
+ * RM-052: o panorama le threads, sessoes e filas deste projeto nesta maquina. Roadmap, reservas e
48
+ * as outras maquinas NAO entram nele; quem le o panorama precisa saber disso para nao concluir
49
+ * "roadmap vazio" a partir de zero threads, que foi o incidente de 29/09.
50
+ */
51
+ function consultaDoMaestro(value, countOtherProjects) {
52
+ const loaded = (0, manifest_1.carregarManifesto)(value.root);
53
+ const others = countOtherProjects ? (0, projeto_alvo_1.outrosProjetosConhecidos)(value.root) : 0;
54
+ return { ...value, project: { ...value.project, root: (0, projeto_alvo_1.raizParaExibir)(value.root),
55
+ remote: (0, projeto_alvo_1.remotoDoProjeto)(value.root, loaded?.manifesto.fabrica?.remoto ?? 'origin') },
56
+ notConsulted: [projeto_alvo_1.FORA_DA_CONSULTA.roadmap, projeto_alvo_1.FORA_DA_CONSULTA.reservas, projeto_alvo_1.FORA_DA_CONSULTA.outrasMaquinas,
57
+ ...(others > 0 ? [`outros projetos desta máquina: ${others} (ork projetos)`] : [])] };
58
+ }
45
59
  const inside = (root, file) => file === root || file.startsWith(root + path.sep);
46
60
  function physical(file) {
47
61
  const absolute = path.resolve(file);
@@ -121,9 +135,9 @@ function discoverMaestro(options) {
121
135
  if (fs.lstatSync(file).isSymbolicLink() && fs.realpathSync(file) !== target)
122
136
  throw Error('maestro.project.scope');
123
137
  }
124
- return context(canonical, 'worktree');
138
+ return consultaDoMaestro(context(canonical, 'worktree'), options.countOtherProjects !== false);
125
139
  }
126
- return context(chosen, origin);
140
+ return consultaDoMaestro(context(chosen, origin), options.countOtherProjects !== false);
127
141
  }
128
142
  function revalidateMaestroContext(value) {
129
143
  const current = context(value.root, value.project.origin);
@@ -51,9 +51,13 @@ function maestroSnapshot(context, options = {}) {
51
51
  coverage: { total: unavailable ? null : all.length, offset, returned: items.length, omitted: unavailable ? null : omitted,
52
52
  nextOffset: omitted ? offset + items.length : null, limit: 50 }, items, gaps: publicMessages(src?.gaps ?? ['maestro.source.unavailable'], 4) };
53
53
  }
54
+ // RM-052: raiz e remoto passam pela mesma redacao dos demais campos publicos.
55
+ const { root, remote } = context.project;
54
56
  const result = { schema: maestro_contract_1.MAESTRO_SCHEMA, observedAt, fingerprint: (0, maestro_sources_1.digest)([context.fingerprint, sources.fingerprint]),
55
- project: { ...context.project, name: (0, maestro_sources_1.safeText)(context.project.name) }, limits: maestro_contract_1.MAESTRO_LIMITS, sections, gaps: publicMessages(sources.gaps, 12),
56
- conflicts: publicMessages([...sources.conflicts, ...(!stable ? ['maestro.snapshot.stale'] : [])], 12) };
57
+ project: { ...context.project, name: (0, maestro_sources_1.safeText)(context.project.name), ...(root === undefined ? {} : { root: (0, maestro_sources_1.safeText)(root) }),
58
+ ...(remote === undefined ? {} : { remote: remote === null ? null : (0, maestro_sources_1.safeText)(remote) }) }, limits: maestro_contract_1.MAESTRO_LIMITS, sections, gaps: publicMessages(sources.gaps, 12),
59
+ conflicts: publicMessages([...sources.conflicts, ...(!stable ? ['maestro.snapshot.stale'] : [])], 12),
60
+ ...(context.notConsulted ? { notConsulted: publicMessages(context.notConsulted, 19) } : {}) };
57
61
  // Orçamento incremental; mantém um item por seção para nextOffset avançar.
58
62
  const sizes = new Map(Object.values(sections).map(s => [s, Buffer.byteLength(JSON.stringify(s))]));
59
63
  let bytes = Buffer.byteLength(JSON.stringify(result));
package/dist/manifest.js CHANGED
@@ -37,10 +37,13 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  };
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.LIMITE_MANIFESTO_BYTES = exports.DIR_ESTADO = exports.NOME_MANIFESTO_LEGADO = exports.NOME_MANIFESTO = void 0;
40
+ exports.EMBEDDING_PADRAO = exports.LIMITE_MANIFESTO_BYTES = exports.DIR_ESTADO = exports.NOME_MANIFESTO_LEGADO = exports.NOME_MANIFESTO = void 0;
41
+ exports.fixarDiretorioDoProjeto = fixarDiretorioDoProjeto;
42
+ exports.diretorioDoProjeto = diretorioDoProjeto;
41
43
  exports.acharRaiz = acharRaiz;
42
44
  exports.dirEstado = dirEstado;
43
45
  exports.validarOptInPlaybook = validarOptInPlaybook;
46
+ exports.configDeEmbedding = configDeEmbedding;
44
47
  exports.carregarManifesto = carregarManifesto;
45
48
  exports.exigirManifesto = exigirManifesto;
46
49
  const fs = __importStar(require("node:fs"));
@@ -53,13 +56,28 @@ const slug_1 = require("./slug");
53
56
  const util_1 = require("./util");
54
57
  const delegation_1 = require("./delegation");
55
58
  const horario_1 = require("./horario");
59
+ const experiencia_1 = require("./experiencia");
60
+ const runtime_ambiente_1 = require("./runtime-ambiente");
61
+ const orkmind_1 = require("./orkmind");
56
62
  exports.NOME_MANIFESTO = 'orkastery.yaml';
57
63
  exports.NOME_MANIFESTO_LEGADO = 'devmaster.yaml';
58
64
  exports.DIR_ESTADO = '.orkastery';
59
65
  /** Limite duro: o manifesto nao pode virar o novo arquivo de 100 KB. */
60
66
  exports.LIMITE_MANIFESTO_BYTES = 16384;
67
+ let diretorioFixado = null;
68
+ /**
69
+ * RM-052: o projeto-alvo fixado pelo CLI (`--projeto`, `ORK_PROJETO`, host sem cwd) vira o ponto
70
+ * de partida padrao das raizes. Sem ele, vale o cwd, como sempre. Quem fixa e `fixarProjetoAlvo`.
71
+ */
72
+ function fixarDiretorioDoProjeto(dir) {
73
+ diretorioFixado = dir;
74
+ }
75
+ /** De onde as raizes padrao partem: o projeto-alvo fixado ou, sem ele, o cwd do processo. */
76
+ function diretorioDoProjeto() {
77
+ return diretorioFixado ?? process.cwd();
78
+ }
61
79
  /** Raiz do projeto: o diretorio que contem o manifesto; senao, a raiz do repositorio git. */
62
- function acharRaiz(dirInicial = process.cwd()) {
80
+ function acharRaiz(dirInicial = diretorioDoProjeto()) {
63
81
  const porManifesto = (0, util_1.subirAte)(dirInicial, exports.NOME_MANIFESTO);
64
82
  if (porManifesto)
65
83
  return porManifesto;
@@ -116,6 +134,31 @@ function prazosDoVerify(verify, erros) {
116
134
  saida.timeout_ms_por_comando = porComando;
117
135
  return saida;
118
136
  }
137
+ /**
138
+ * RM-037 (rm037defeito, defeito 5): `ci.external_repositories`, mapa `dono/nome` para o check exigido no
139
+ * head do PR (texto vazio ou nulo: repositorio sem CI). Forma invalida e erro do manifesto, nunca adivinhada.
140
+ */
141
+ function repositoriosExternos(v, erros) {
142
+ if (v === undefined || v === null)
143
+ return {};
144
+ if (typeof v !== 'object' || Array.isArray(v)) {
145
+ erros.push('ci.external_repositories deve ser um mapa "dono/nome": "check exigido" (vazio para repositorio sem CI)');
146
+ return {};
147
+ }
148
+ const r = {};
149
+ for (const [repo, check] of Object.entries(v)) {
150
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo)) {
151
+ erros.push(`ci.external_repositories: "${repo}" nao e dono/nome do GitHub`);
152
+ continue;
153
+ }
154
+ if (check !== null && typeof check !== 'string') {
155
+ erros.push(`ci.external_repositories.${repo}: o check exigido e texto (vazio para sem CI)`);
156
+ continue;
157
+ }
158
+ r[repo] = (check ?? '').trim();
159
+ }
160
+ return r;
161
+ }
119
162
  function booleano(v, padrao) {
120
163
  return typeof v === 'boolean' ? v : padrao;
121
164
  }
@@ -133,6 +176,81 @@ function validarOptInPlaybook(valor) {
133
176
  }
134
177
  return valor.ativo;
135
178
  }
179
+ /** I-38 (D5): sem o bloco `memory.embedding`, os embeddings ficam desligados. */
180
+ exports.EMBEDDING_PADRAO = Object.freeze({
181
+ provider: 'none', model: '', dim: 1024, api_key_env: '', fallback_model: '', max_tokens_por_execucao: 1_000_000,
182
+ });
183
+ const CHAVES_DE_EMBEDDING = ['provider', 'model', 'dim', 'api_key_env', 'fallback_model', 'max_tokens_por_execucao'];
184
+ /** Valor que parece segredo (URL, prefixo de chave, sequencia longa) nunca e repetido num erro. */
185
+ function exibir(v) {
186
+ const t = typeof v === 'string' ? v : JSON.stringify(v);
187
+ return /:\/\/|\bsk-|[A-Za-z0-9_-]{32,}/.test(t) ? '(valor omitido: parece segredo)' : JSON.stringify(v);
188
+ }
189
+ /**
190
+ * I-38 (D5): le e valida `memory.embedding`. A chave so entra pelo NOME da variavel: valor com
191
+ * cara de chave ou DSN reprova o manifesto, e nome da lista de provider pago tambem, porque a
192
+ * entrada do `ork` apagaria a variavel sob `subscription-only` e o preflight reprovaria o codex.
193
+ */
194
+ function lerEmbedding(bruto, variavelDaDsn, erros) {
195
+ if (bruto === undefined || bruto === null)
196
+ return undefined;
197
+ if (typeof bruto !== 'object' || Array.isArray(bruto)) {
198
+ erros.push('memory.embedding precisa ser um bloco com provider, model, dim, api_key_env, fallback_model e max_tokens_por_execucao');
199
+ return undefined;
200
+ }
201
+ const e = bruto;
202
+ for (const chave of Object.keys(e)) {
203
+ if (!CHAVES_DE_EMBEDDING.includes(chave)) {
204
+ erros.push(`memory.embedding.${chave} nao existe (a chave entra pelo ambiente: declare so o NOME em api_key_env)`);
205
+ }
206
+ }
207
+ const provider = texto(e.provider, exports.EMBEDDING_PADRAO.provider).trim();
208
+ if (provider !== 'none' && provider !== 'openrouter') {
209
+ erros.push(`memory.embedding.provider invalido: ${exibir(provider)} (aceitos: none, openrouter)`);
210
+ }
211
+ const model = texto(e.model, '').trim();
212
+ const apiKeyEnv = texto(e.api_key_env, '').trim();
213
+ const fallback = texto(e.fallback_model, '').trim();
214
+ const inteiro = (chave, v, padrao, min, max) => {
215
+ if (v === undefined || v === null)
216
+ return padrao;
217
+ const n = typeof v === 'number' ? v : typeof v === 'string' && v.trim() !== '' ? Number(v) : NaN;
218
+ if (!Number.isInteger(n) || n < min || n > max) {
219
+ erros.push(`memory.embedding.${chave} precisa ser inteiro entre ${min} e ${max} (recebido ${exibir(v)})`);
220
+ return padrao;
221
+ }
222
+ return n;
223
+ };
224
+ const dim = inteiro('dim', e.dim, exports.EMBEDDING_PADRAO.dim, 32, 4096);
225
+ const teto = inteiro('max_tokens_por_execucao', e.max_tokens_por_execucao, exports.EMBEDDING_PADRAO.max_tokens_por_execucao, 1, 10_000_000);
226
+ if (apiKeyEnv.includes('://') || /^sk-/i.test(apiKeyEnv)) {
227
+ erros.push('memory.embedding.api_key_env recebe o NOME da variavel de ambiente com a chave, nunca o valor da chave nem uma DSN');
228
+ }
229
+ else if (apiKeyEnv !== '' && !/^[A-Z][A-Z0-9_]*$/.test(apiKeyEnv)) {
230
+ erros.push('memory.embedding.api_key_env invalido: esperado nome de variavel de ambiente, [A-Z][A-Z0-9_]* (o valor recebido nao e repetido)');
231
+ }
232
+ else if (runtime_ambiente_1.ENVS_DE_PROVIDER_PAGO.includes(apiKeyEnv)) {
233
+ erros.push(`memory.embedding.api_key_env nao pode ser ${apiKeyEnv}: nomes de provider pago sao removidos sob subscription-only; use uma chave dedicada com nome proprio`);
234
+ }
235
+ else if (apiKeyEnv !== '' && apiKeyEnv === variavelDaDsn) {
236
+ erros.push('memory.embedding.api_key_env nao pode repetir memory.database_url_env: a DSN nunca vai ao provider');
237
+ }
238
+ if (provider === 'openrouter') {
239
+ if (!orkmind_1.MODELO_DE_EMBEDDING.test(model))
240
+ erros.push(`memory.embedding.model invalido: ${exibir(model)} (esperado org/nome, por exemplo qwen/qwen3-embedding-8b)`);
241
+ if (apiKeyEnv === '')
242
+ erros.push('memory.embedding.api_key_env e obrigatorio com provider openrouter (o NOME da variavel com a chave dedicada)');
243
+ }
244
+ if (fallback !== '' && !orkmind_1.MODELO_DE_EMBEDDING.test(fallback)) {
245
+ erros.push(`memory.embedding.fallback_model invalido: ${exibir(fallback)} (esperado org/nome de um modelo do Hugging Face)`);
246
+ }
247
+ return { provider: provider === 'openrouter' ? 'openrouter' : 'none', model, dim, api_key_env: apiKeyEnv,
248
+ fallback_model: fallback, max_tokens_por_execucao: teto };
249
+ }
250
+ /** I-38 (D5): leitura unica da configuracao de embedding, com o padrao `none`. */
251
+ function configDeEmbedding(manifesto) {
252
+ return { ...exports.EMBEDDING_PADRAO, ...(manifesto.memory.embedding ?? {}) };
253
+ }
136
254
  /** I-33 (N2): texto e numero que dizem sim ou nao sem ambiguidade; o resto nao e reconhecido. */
137
255
  const BOOLEANOS_RECONHECIVEIS = new Map([
138
256
  ['true', true], ['yes', true], ['on', true], ['sim', true], ['1', true],
@@ -146,7 +264,7 @@ function booleanoReconhecivel(v) {
146
264
  return BOOLEANOS_RECONHECIVEIS.get(v.trim().toLowerCase()) ?? null;
147
265
  }
148
266
  /** Le e valida o manifesto. Nunca lanca: erros e avisos voltam na estrutura. */
149
- function carregarManifesto(dirInicial = process.cwd()) {
267
+ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
150
268
  const raiz = acharRaiz(dirInicial);
151
269
  const candidatos = [
152
270
  { caminho: path.join(raiz, exports.NOME_MANIFESTO), legado: false },
@@ -171,6 +289,22 @@ function carregarManifesto(dirInicial = process.cwd()) {
171
289
  const fusoDoDono = (0, horario_1.lerFusoDoDono)(mapa(dados.owner).timezone);
172
290
  if (fusoDoDono.aviso)
173
291
  avisos.push(fusoDoDono.aviso);
292
+ // Preferência inválida segue o fuso: avisa e cai no padrão, sem parar os comandos (e o próprio
293
+ // onboarding set, que é o caminho para corrigir).
294
+ const preferencias = {};
295
+ for (const chave of ['language', 'depth', 'experience']) {
296
+ const valor = mapa(dados.owner)[chave];
297
+ if (valor === undefined)
298
+ continue;
299
+ try {
300
+ Object.assign(preferencias, (0, experiencia_1.validarPreferencias)({ [chave]: valor }));
301
+ }
302
+ catch (e) {
303
+ avisos.push(`${e.message}; vale o padrão até corrigir`);
304
+ }
305
+ }
306
+ if (fusoDoDono.origem === 'manifesto')
307
+ preferencias.timezone = fusoDoDono.fuso;
174
308
  const board = mapa(dados.board);
175
309
  const runtime = mapa(dados.runtime);
176
310
  const conduction = mapa(dados.conduction);
@@ -299,6 +433,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
299
433
  avisos.push('memory.mode: orkmind sem memory.database_url_env: o regime efetivo cai para files ' +
300
434
  '(o `ork` nao adivinha DSN, e base alheia e pior do que base nenhuma)');
301
435
  }
436
+ const embedding = lerEmbedding(memory.embedding, variavelDaDsn, erros);
302
437
  const bytes = Buffer.byteLength(bruto, 'utf8');
303
438
  if (bytes > exports.LIMITE_MANIFESTO_BYTES) {
304
439
  erros.push(`manifesto com ${bytes} bytes, acima do limite duro de ${exports.LIMITE_MANIFESTO_BYTES} (prosa vai para memoria com tag, nao para o YAML)`);
@@ -313,7 +448,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
313
448
  stage: texto(project.stage, 'nascente') ?? 'nascente',
314
449
  repo_root: texto(project.repo_root, raiz),
315
450
  },
316
- ...(fusoDoDono.origem === 'manifesto' ? { owner: { timezone: fusoDoDono.fuso } } : {}),
451
+ ...(Object.keys(preferencias).length ? { owner: preferencias } : {}),
317
452
  board: {
318
453
  adapter: texto(board.adapter, 'hermes-kanban'),
319
454
  default: texto(board.default, 'default'),
@@ -350,6 +485,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
350
485
  required_for_ship: booleano(ci.required_for_ship, false),
351
486
  context: texto(ci.context, 'ork-verify'),
352
487
  command: ci.command === null || ci.command === undefined ? undefined : texto(ci.command, ''),
488
+ external_repositories: repositoriosExternos(ci.external_repositories, erros),
353
489
  },
354
490
  concurrency: {
355
491
  max_parallel_threads: numero(concurrency.max_parallel_threads, 3),
@@ -382,6 +518,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
382
518
  cli: texto(memory.cli, 'orkmind'),
383
519
  tenant: texto(memory.tenant, '') || nomeProjeto,
384
520
  timeout_ms: numero(memory.timeout_ms, 15000),
521
+ ...(embedding ? { embedding } : {}),
385
522
  },
386
523
  // Bloco B5: governanca de custo dos auditores. Ausente no manifesto quer dizer
387
524
  // "nao declarada", nao "sem limite": a rodada avisa que so `eco` e o escopo
@@ -402,7 +539,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
402
539
  return { caminho: achado.caminho, raiz, legado: achado.legado, bytes, manifesto, erros, avisos };
403
540
  }
404
541
  /** Carrega o manifesto ou encerra com mensagem acionavel (uso nos subcomandos). */
405
- function exigirManifesto(dirInicial = process.cwd()) {
542
+ function exigirManifesto(dirInicial = diretorioDoProjeto()) {
406
543
  const carregado = carregarManifesto(dirInicial);
407
544
  if (!carregado) {
408
545
  throw new Error(`manifesto ${exports.NOME_MANIFESTO} nao encontrado a partir de ${dirInicial}. Rode: ork init`);