@orkastery/cli 0.4.2 → 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 (128) 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 -3
  23. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  24. package/adapters/openclaw/dist/index.js +92 -39
  25. package/adapters/openclaw/openclaw.plugin.json +10 -3
  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 -41
  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 +88 -9
  34. package/dist/adapters/codex-events.js +3 -1
  35. package/dist/board.js +74 -0
  36. package/dist/branch-de-estado.js +5 -4
  37. package/dist/busca-semantica.js +161 -0
  38. package/dist/ci.js +141 -10
  39. package/dist/company-brain-cli.js +27 -2
  40. package/dist/company-brain-context.js +132 -0
  41. package/dist/company-brain-dossie.js +376 -0
  42. package/dist/company-brain-mcp.js +4 -1
  43. package/dist/conducao.js +117 -31
  44. package/dist/decisao-autonoma.js +42 -0
  45. package/dist/docs.js +64 -22
  46. package/dist/doctor.js +33 -0
  47. package/dist/entrega-pr.js +109 -5
  48. package/dist/experiencia-instalacao.js +256 -0
  49. package/dist/experiencia.js +72 -0
  50. package/dist/fabrica-estado.js +43 -10
  51. package/dist/fechamento.js +105 -0
  52. package/dist/fix.js +1 -1
  53. package/dist/forja.js +435 -0
  54. package/dist/gates.js +10 -1
  55. package/dist/hitl-classificacao.js +11 -1
  56. package/dist/hitl-contract.js +50 -7
  57. package/dist/hitl-curto.js +158 -0
  58. package/dist/hitl-gates.js +67 -1
  59. package/dist/hitl-lote.js +30 -15
  60. package/dist/hitl-presentation.js +10 -7
  61. package/dist/hitl-resumo.js +33 -12
  62. package/dist/hitl-texto-livre.js +150 -0
  63. package/dist/hosts.js +86 -1
  64. package/dist/index.js +605 -41
  65. package/dist/indice-vetorial.js +317 -0
  66. package/dist/init.js +13 -0
  67. package/dist/integracoes-locais.js +1 -1
  68. package/dist/intelligence-benchmark-contract.js +570 -0
  69. package/dist/intelligence-graph-cli.js +430 -0
  70. package/dist/intelligence-graph-contract.js +577 -0
  71. package/dist/intelligence-graph-extract-md.js +469 -0
  72. package/dist/intelligence-graph-extract-ts.js +810 -0
  73. package/dist/intelligence-graph-extract.js +369 -0
  74. package/dist/intelligence-graph-index.js +512 -0
  75. package/dist/intelligence-graph-parsers.js +327 -0
  76. package/dist/intelligence-graph-query.js +344 -0
  77. package/dist/intelligence-graph-repo.js +187 -0
  78. package/dist/leases.js +84 -0
  79. package/dist/ledger.js +14 -0
  80. package/dist/maestro-cli.js +10 -1
  81. package/dist/maestro-contract.js +5 -1
  82. package/dist/maestro-discovery.js +16 -2
  83. package/dist/maestro-snapshot.js +6 -2
  84. package/dist/manifest.js +142 -5
  85. package/dist/master-batch.js +3 -2
  86. package/dist/master-digest.js +2 -2
  87. package/dist/master-nota.js +203 -0
  88. package/dist/master.js +4 -0
  89. package/dist/mcp-experiencia.js +164 -0
  90. package/dist/mcp-git.js +36 -10
  91. package/dist/mcp-install.js +3 -1
  92. package/dist/mcp-maestro.js +3 -2
  93. package/dist/mcp-server.js +84 -5
  94. package/dist/memoria.js +147 -1
  95. package/dist/network-roadmap.js +907 -0
  96. package/dist/ocupacao.js +38 -2
  97. package/dist/onboarding.js +113 -7
  98. package/dist/orkmind.js +165 -9
  99. package/dist/phase.js +166 -13
  100. package/dist/policies.js +2 -0
  101. package/dist/projeto-alvo.js +551 -0
  102. package/dist/prompts.js +1 -1
  103. package/dist/pulse-consentimento.js +4 -2
  104. package/dist/pulse-delivery.js +5 -0
  105. package/dist/pulse-resposta.js +674 -49
  106. package/dist/pulse.js +21 -0
  107. package/dist/ratelimit.js +3 -2
  108. package/dist/retry.js +230 -9
  109. package/dist/roadmap-reservas.js +262 -20
  110. package/dist/roadmap-status.js +213 -0
  111. package/dist/runtime-ambiente.js +12 -1
  112. package/dist/session-watcher-claude.js +26 -0
  113. package/dist/sessoes.js +49 -15
  114. package/dist/thread-close.js +3 -0
  115. package/dist/verify.js +5 -4
  116. package/dist/worktree.js +77 -0
  117. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  118. package/eval/casos/orchestration-experience.json +48 -0
  119. package/package.json +1 -1
  120. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  121. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  122. package/schemas/maestro-snapshot.schema.json +23 -0
  123. package/skills/README.md +7 -5
  124. package/skills/core/onboarding/SKILL.md +7 -0
  125. package/skills/core/orchestration-experience/SKILL.md +113 -0
  126. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  127. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  128. package/skills/phases/go-implementation/SKILL.md +2 -0
@@ -0,0 +1,551 @@
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.ORIGENS_DO_PROJETO = exports.FORA_DA_CONSULTA = exports.OFERTA_DA_REDE = exports.ErroDeProjeto = exports.PADRAO_DO_NOME_DE_PROJETO = exports.SAIDA_DE_PROJETO = exports.ENV_PROJETO_EXPLICITO = exports.ENV_PROJETO = exports.CONTRATO_CONSULTA = exports.CONTRATO_PROJETOS = void 0;
37
+ exports.caminhoDoRegistro = caminhoDoRegistro;
38
+ exports.lerRegistroDeProjetos = lerRegistroDeProjetos;
39
+ exports.listarProjetos = listarProjetos;
40
+ exports.raizCanonica = raizCanonica;
41
+ exports.remotoSemCredencial = remotoSemCredencial;
42
+ exports.remotoDoProjeto = remotoDoProjeto;
43
+ exports.registrarProjeto = registrarProjeto;
44
+ exports.registrarProjetoEmSilencio = registrarProjetoEmSilencio;
45
+ exports.esquecerProjeto = esquecerProjeto;
46
+ exports.candidatosDoHost = candidatosDoHost;
47
+ exports.resolverProjetoAlvo = resolverProjetoAlvo;
48
+ exports.fixarProjetoAlvo = fixarProjetoAlvo;
49
+ exports.projetoAlvoAtual = projetoAlvoAtual;
50
+ exports.raizParaExibir = raizParaExibir;
51
+ exports.outrosProjetosConhecidos = outrosProjetosConhecidos;
52
+ exports.semRemoto = semRemoto;
53
+ exports.consultaDoProjeto = consultaDoProjeto;
54
+ exports.linhasDaConsulta = linhasDaConsulta;
55
+ /**
56
+ * RM-052: o projeto-alvo explicito.
57
+ *
58
+ * Em 29/09/2026 o dono pediu pelo Telegram o status do roadmap do orkastery e recebeu o panorama de
59
+ * `~/.openclaw/workspace`: as tools do host chamavam o `ork` sem projeto, o nucleo resolvia pelo
60
+ * `process.cwd()` do gateway e a saida nao dizia qual projeto tinha lido. Este modulo e a correcao
61
+ * no nucleo, em tres pecas:
62
+ *
63
+ * 1. o REGISTRO dos projetos desta maquina, `~/.orkastery/projetos.json` (contrato
64
+ * `ork.projetos/v1`), alimentado por `ork init`, `ork thread new` e `ork fabrica entrar`. So
65
+ * identificadores: nome, abbrev, raiz, remoto sem credencial e datas. Nenhum segredo;
66
+ * 2. a RESOLUCAO do projeto-alvo, com precedencia explicita sobre o cwd:
67
+ * `--projeto` > `ORK_PROJETO` > host sem cwd (`ORK_PROJETO_EXPLICITO=1`) > cwd. Nome ambiguo ou
68
+ * desconhecido recusa com `ErroDeProjeto` e a lista de candidatos; o nucleo nunca chuta;
69
+ * 3. o CABECALHO da consulta (`ork.consulta/v1`): qual projeto foi lido, por que ele, e o que NAO
70
+ * foi lido, para nenhum leitor concluir "roadmap vazio" a partir de um board sem threads.
71
+ *
72
+ * O registro e por MAQUINA e nao reusa o portfolio da RM-019 (D1): o portfolio e estado canonico de
73
+ * UM projeto e nao conhece as raizes locais das copias. RM-053 e RM-054 leem este contrato.
74
+ */
75
+ const fs = __importStar(require("node:fs"));
76
+ const os = __importStar(require("node:os"));
77
+ const path = __importStar(require("node:path"));
78
+ const estado_thread_1 = require("./estado-thread");
79
+ const manifest_1 = require("./manifest");
80
+ const maquina_1 = require("./maquina");
81
+ const policies_1 = require("./policies");
82
+ const redacao_url_1 = require("./redacao-url");
83
+ const util_1 = require("./util");
84
+ exports.CONTRATO_PROJETOS = 'ork.projetos/v1';
85
+ exports.CONTRATO_CONSULTA = 'ork.consulta/v1';
86
+ /** O projeto pedido pelo ambiente, abaixo de `--projeto` e acima do cwd. */
87
+ exports.ENV_PROJETO = 'ORK_PROJETO';
88
+ /** Declarado pelos adaptadores de host: o cwd do gateway nao e projeto de ninguem (D3). */
89
+ exports.ENV_PROJETO_EXPLICITO = 'ORK_PROJETO_EXPLICITO';
90
+ /** Codigo de saida da recusa de projeto-alvo no CLI (D3). */
91
+ exports.SAIDA_DE_PROJETO = 4;
92
+ const ARQUIVO = 'projetos.json';
93
+ const TRAVA = 'projetos.json.lock';
94
+ /** Nome ou abbrev de projeto: o mesmo alfabeto dos identificadores de thread, sem barra. */
95
+ exports.PADRAO_DO_NOME_DE_PROJETO = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
96
+ /** A recusa tipada: o texto para o humano, o JSON para o host, os candidatos para escolher. */
97
+ class ErroDeProjeto extends Error {
98
+ codigo;
99
+ detalhe;
100
+ candidatos;
101
+ correcao;
102
+ constructor(codigo, detalhe, candidatos, correcao) {
103
+ super(`${codigo}: ${detalhe}`);
104
+ this.codigo = codigo;
105
+ this.detalhe = detalhe;
106
+ this.candidatos = candidatos;
107
+ this.correcao = correcao;
108
+ this.name = 'ErroDeProjeto';
109
+ }
110
+ get recusa() {
111
+ return { erro: this.codigo, detalhe: this.detalhe, candidatos: this.candidatos, correcao: this.correcao };
112
+ }
113
+ get texto() {
114
+ return [
115
+ `${this.codigo}: ${this.detalhe}`,
116
+ ...(this.candidatos.length ? ['Candidatos:', ...this.candidatos.map((c) => ` • ${linhaDoCandidato(c)}`)] : []),
117
+ this.correcao,
118
+ ].join('\n');
119
+ }
120
+ }
121
+ exports.ErroDeProjeto = ErroDeProjeto;
122
+ function linhaDoCandidato(c) {
123
+ return `${c.nome} (${c.abbrev || '-'}) · ${c.raiz} · ${c.remoto ?? 'sem remoto'}${c.presente ? '' : ' · ausente do disco'}`;
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // O registro.
127
+ // ---------------------------------------------------------------------------
128
+ function caminhoDoRegistro() {
129
+ return path.join((0, maquina_1.pastaDoUsuario)(), ARQUIVO);
130
+ }
131
+ const iso = (v) => typeof v === 'string' && Number.isFinite(Date.parse(v));
132
+ const textoCurto = (v, teto) => typeof v === 'string' && !!v.trim() && v.length <= teto &&
133
+ !/[\x00-\x1f\x7f]/.test(v);
134
+ const FONTES = ['init', 'thread new', 'fabrica entrar', 'projetos registrar', 'mcp thread new'];
135
+ function entradaValida(v) {
136
+ if (!v || typeof v !== 'object' || Array.isArray(v))
137
+ return false;
138
+ const e = v;
139
+ return textoCurto(e.nome, 120) && typeof e.abbrev === 'string' && e.abbrev.length <= 16 &&
140
+ textoCurto(e.raiz, 4096) && path.isAbsolute(e.raiz) &&
141
+ (e.remoto === null || textoCurto(e.remoto, 2048)) && iso(e.registradoEm) && iso(e.atualizadoEm) &&
142
+ FONTES.includes(e.fonte);
143
+ }
144
+ /** Leitura tolerante: arquivo ausente, corrompido ou de outro contrato vale registro vazio. */
145
+ function lerRegistroDeProjetos() {
146
+ const vazio = { contrato: exports.CONTRATO_PROJETOS, atualizadoEm: null, projetos: [] };
147
+ let bruto;
148
+ try {
149
+ bruto = JSON.parse(fs.readFileSync(caminhoDoRegistro(), 'utf8'));
150
+ }
151
+ catch {
152
+ return vazio;
153
+ }
154
+ if (!bruto || typeof bruto !== 'object' || bruto.contrato !== exports.CONTRATO_PROJETOS)
155
+ return vazio;
156
+ const projetos = bruto.projetos;
157
+ const atualizadoEm = bruto.atualizadoEm;
158
+ return { contrato: exports.CONTRATO_PROJETOS, atualizadoEm: iso(atualizadoEm) ? atualizadoEm : null,
159
+ projetos: Array.isArray(projetos) ? projetos.filter(entradaValida) : [] };
160
+ }
161
+ /** O manifesto ainda esta na raiz registrada? */
162
+ function manifestoPresente(raiz) {
163
+ return [manifest_1.NOME_MANIFESTO, manifest_1.NOME_MANIFESTO_LEGADO].some((n) => fs.existsSync(path.join(raiz, n)));
164
+ }
165
+ function listarProjetos() {
166
+ return lerRegistroDeProjetos().projetos.map((p) => ({ ...p, presente: manifestoPresente(p.raiz) }))
167
+ .sort((a, b) => a.nome.localeCompare(b.nome) || a.raiz.localeCompare(b.raiz));
168
+ }
169
+ /** A raiz canonica de uma copia: a arvore principal da worktree, com caminho real. */
170
+ function raizCanonica(raiz) {
171
+ const real = fs.realpathSync(path.resolve(raiz));
172
+ try {
173
+ return fs.realpathSync((0, estado_thread_1.raizDoEstado)(real));
174
+ }
175
+ catch {
176
+ return real;
177
+ }
178
+ }
179
+ /**
180
+ * A URL do remoto sem usuario nem senha: saem inteiros, nao so redigidos, como a RM-053 le o
181
+ * registro. A forma scp do git (`git@host:dono/repo.git`) e caminho local ficam como estao: o
182
+ * usuario de transporte nao e segredo. URL que nao se deixa ler cai na redacao de sempre.
183
+ */
184
+ function remotoSemCredencial(url) {
185
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url))
186
+ return url;
187
+ try {
188
+ const u = new URL(url);
189
+ if (!u.username && !u.password)
190
+ return url;
191
+ u.username = '';
192
+ u.password = '';
193
+ return u.toString();
194
+ }
195
+ catch {
196
+ return (0, redacao_url_1.redigirCredenciaisUrl)(url);
197
+ }
198
+ }
199
+ /** URL do remoto, sem credencial. `null` quando o projeto nao tem aquele remoto. */
200
+ function remotoDoProjeto(raiz, remoto = 'origin') {
201
+ const r = (0, util_1.exec)('git', ['remote', 'get-url', remoto], raiz, 5000);
202
+ const url = r.ok ? r.stdout.trim() : '';
203
+ return url ? remotoSemCredencial(url) : null;
204
+ }
205
+ /**
206
+ * Trava curta do registro. O registro e um indice de conveniencia (D7): quem segura a trava e um
207
+ * processo vivo; trava de processo que ja morreu e retomada, porque ninguem mais vai solta-la.
208
+ */
209
+ function comTrava(alterar, esperaMs = 2000) {
210
+ const dir = (0, maquina_1.pastaDoUsuario)(), trava = path.join(dir, TRAVA);
211
+ fs.mkdirSync(dir, { recursive: true });
212
+ const limite = Date.now() + esperaMs;
213
+ let fd;
214
+ for (;;) {
215
+ try {
216
+ fd = fs.openSync(trava, 'wx', 0o600);
217
+ break;
218
+ }
219
+ catch (e) {
220
+ if (e.code !== 'EEXIST')
221
+ throw e;
222
+ let dono = 0;
223
+ try {
224
+ dono = Number(JSON.parse(fs.readFileSync(trava, 'utf8')).pid);
225
+ }
226
+ catch { /* trava sendo escrita */ }
227
+ if (Number.isSafeInteger(dono) && dono > 0 && !processoVivo(dono)) {
228
+ fs.rmSync(trava, { force: true });
229
+ continue;
230
+ }
231
+ if (Date.now() >= limite)
232
+ throw new Error(`registro de projetos ocupado (${trava}); tente de novo`);
233
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
234
+ }
235
+ }
236
+ try {
237
+ fs.writeSync(fd, JSON.stringify({ pid: process.pid, em: new Date().toISOString() }));
238
+ return alterar();
239
+ }
240
+ finally {
241
+ fs.closeSync(fd);
242
+ fs.rmSync(trava, { force: true });
243
+ }
244
+ }
245
+ function processoVivo(pid) {
246
+ try {
247
+ process.kill(pid, 0);
248
+ return true;
249
+ }
250
+ catch (e) {
251
+ return e.code === 'EPERM';
252
+ }
253
+ }
254
+ function gravarRegistro(registro) {
255
+ const dir = (0, maquina_1.pastaDoUsuario)(), destino = caminhoDoRegistro();
256
+ const conteudo = JSON.stringify(registro, null, 2) + '\n';
257
+ // Varredura final: o arquivo nunca carrega credencial, nem por um remoto mal redigido.
258
+ if ((0, policies_1.procurarSegredos)(conteudo).length)
259
+ throw new Error('registro de projetos recusado: o conteudo parece carregar segredo');
260
+ const temp = path.join(dir, `.${ARQUIVO}.${process.pid}.${Date.now()}.tmp`);
261
+ try {
262
+ fs.writeFileSync(temp, conteudo, { mode: 0o600, flag: 'wx' });
263
+ fs.renameSync(temp, destino);
264
+ }
265
+ finally {
266
+ fs.rmSync(temp, { force: true });
267
+ }
268
+ }
269
+ /**
270
+ * O projeto de um caminho qualquer dentro dele: a raiz do manifesto e, quando ela e a worktree de
271
+ * uma thread, a arvore principal (se a principal tem o proprio manifesto). `null` sem manifesto.
272
+ */
273
+ function projetoDoCaminho(caminho) {
274
+ const local = (0, manifest_1.carregarManifesto)(caminho);
275
+ if (!local)
276
+ return null;
277
+ const raizLocal = fs.realpathSync(local.raiz), canonica = raizCanonica(raizLocal);
278
+ if (canonica !== raizLocal) {
279
+ const principal = (0, manifest_1.carregarManifesto)(canonica);
280
+ if (principal && fs.realpathSync(principal.raiz) === canonica)
281
+ return { raiz: canonica, carregado: principal };
282
+ }
283
+ return { raiz: raizLocal, carregado: local };
284
+ }
285
+ /**
286
+ * Registra (ou atualiza) o projeto do caminho informado. A raiz vira a canonica: registrar de dentro
287
+ * da worktree de uma thread registra a arvore principal.
288
+ */
289
+ function registrarProjeto(caminho, fonte, opcoes = {}) {
290
+ const achado = projetoDoCaminho(caminho);
291
+ if (!achado) {
292
+ throw new ErroDeProjeto('projeto.sem-manifesto', `nenhum manifesto do ork em ${raizParaExibir(path.resolve(caminho))}`, [], 'Rode `ork init` na raiz do projeto antes de registrá-lo.');
293
+ }
294
+ const { raiz: canonica, carregado } = achado;
295
+ if (carregado.erros.length)
296
+ throw new Error(`registro de projetos: manifesto inválido em ${carregado.caminho}`);
297
+ const quando = opcoes.quando ?? new Date().toISOString();
298
+ const novo = {
299
+ nome: carregado.manifesto.project.name, abbrev: carregado.manifesto.project.abbrev, raiz: canonica,
300
+ remoto: remotoDoProjeto(canonica, carregado.manifesto.fabrica?.remoto ?? 'origin'), atualizadoEm: quando, fonte,
301
+ };
302
+ if ((0, policies_1.procurarSegredos)(JSON.stringify(novo)).length) {
303
+ throw new Error('registro de projetos recusado: a entrada parece carregar segredo');
304
+ }
305
+ return comTrava(() => {
306
+ const registro = lerRegistroDeProjetos();
307
+ const anterior = registro.projetos.find((p) => p.raiz === canonica);
308
+ const entrada = { ...novo, registradoEm: anterior?.registradoEm ?? quando };
309
+ registro.projetos = [...registro.projetos.filter((p) => p.raiz !== canonica), entrada]
310
+ .sort((a, b) => a.nome.localeCompare(b.nome) || a.raiz.localeCompare(b.raiz));
311
+ registro.atualizadoEm = quando;
312
+ gravarRegistro(registro);
313
+ return entrada;
314
+ }, opcoes.esperaMs);
315
+ }
316
+ /**
317
+ * D7: o registro nunca derruba o comando que o alimenta. Devolve o aviso para o terminal, ou `null`.
318
+ */
319
+ function registrarProjetoEmSilencio(raiz, fonte) {
320
+ try {
321
+ registrarProjeto(raiz, fonte);
322
+ return null;
323
+ }
324
+ catch (e) {
325
+ return `aviso: projeto não registrado em ${caminhoDoRegistro()} (${e.message})`;
326
+ }
327
+ }
328
+ /** Tira do registro por nome, abbrev ou caminho. Nada casou e recusa, com os registrados. */
329
+ function esquecerProjeto(alvo, cwd = process.cwd()) {
330
+ const valor = alvo.trim();
331
+ return comTrava(() => {
332
+ const registro = lerRegistroDeProjetos();
333
+ const porCaminho = ehCaminho(valor) ? caminhoPossivel(valor, cwd) : null;
334
+ const sai = registro.projetos.filter((p) => porCaminho ? p.raiz === porCaminho : casaNome(p, valor));
335
+ if (!sai.length) {
336
+ throw new ErroDeProjeto('projeto.desconhecido', `"${valor}" não está no registro de projetos desta máquina`, registro.projetos.map(candidatoDoRegistro), 'Veja os registrados com `ork projetos`.');
337
+ }
338
+ registro.projetos = registro.projetos.filter((p) => !sai.includes(p));
339
+ registro.atualizadoEm = new Date().toISOString();
340
+ gravarRegistro(registro);
341
+ return sai;
342
+ });
343
+ }
344
+ // ---------------------------------------------------------------------------
345
+ // A resolucao.
346
+ // ---------------------------------------------------------------------------
347
+ function ehCaminho(valor) {
348
+ return valor.includes('/') || valor.startsWith('.') || valor.startsWith('~') || path.isAbsolute(valor);
349
+ }
350
+ function expandirHome(valor) {
351
+ return valor === '~' ? os.homedir() : valor.startsWith('~/') ? path.join(os.homedir(), valor.slice(2)) : valor;
352
+ }
353
+ /** Caminho real quando existe; o resolvido quando nao (esquecer uma raiz que sumiu do disco). */
354
+ function caminhoPossivel(valor, cwd) {
355
+ const resolvido = path.resolve(cwd, expandirHome(valor));
356
+ try {
357
+ return fs.realpathSync(resolvido);
358
+ }
359
+ catch {
360
+ return resolvido;
361
+ }
362
+ }
363
+ function casaNome(p, valor) {
364
+ const v = valor.toLowerCase();
365
+ return p.nome.toLowerCase() === v || (!!p.abbrev && p.abbrev.toLowerCase() === v);
366
+ }
367
+ function candidatoDoRegistro(p) {
368
+ return { nome: p.nome, abbrev: p.abbrev, raiz: raizParaExibir(p.raiz), remoto: p.remoto, presente: manifestoPresente(p.raiz) };
369
+ }
370
+ /** A raiz do manifesto a partir de um caminho qualquer dentro do projeto. */
371
+ function raizPorCaminho(valor, cwd) {
372
+ const dir = path.resolve(cwd, expandirHome(valor));
373
+ if (!fs.existsSync(dir)) {
374
+ throw new ErroDeProjeto('projeto.sem-manifesto', `o caminho ${raizParaExibir(dir)} não existe`, [], 'Informe o caminho da raiz de um projeto com orkastery.yaml, ou o nome de um projeto registrado (`ork projetos`).');
375
+ }
376
+ const achada = (0, util_1.subirAte)(dir, manifest_1.NOME_MANIFESTO) ?? (0, util_1.subirAte)(dir, manifest_1.NOME_MANIFESTO_LEGADO);
377
+ if (!achada) {
378
+ throw new ErroDeProjeto('projeto.sem-manifesto', `nenhum orkastery.yaml em ${raizParaExibir(dir)} nem acima dele`, [], 'Rode `ork init` na raiz do projeto, ou informe o nome de um projeto registrado (`ork projetos`).');
379
+ }
380
+ return fs.realpathSync(achada);
381
+ }
382
+ function raizPorNome(valor) {
383
+ if (!exports.PADRAO_DO_NOME_DE_PROJETO.test(valor)) {
384
+ throw new ErroDeProjeto('projeto.desconhecido', `"${valor}" não é um nome de projeto válido`, lerRegistroDeProjetos().projetos.map(candidatoDoRegistro), 'Use o nome ou o abbrev de um projeto de `ork projetos`.');
385
+ }
386
+ const registrados = lerRegistroDeProjetos().projetos;
387
+ const casados = registrados.filter((p) => casaNome(p, valor));
388
+ const presentes = casados.filter((p) => manifestoPresente(p.raiz));
389
+ if (presentes.length === 1)
390
+ return presentes[0].raiz;
391
+ if (presentes.length > 1) {
392
+ throw new ErroDeProjeto('projeto.ambiguo', `"${valor}" casa ${presentes.length} projetos registrados nesta máquina`, presentes.map(candidatoDoRegistro), 'Repita com o caminho da raiz (`--projeto <caminho>`), ou tire a cópia que sobra com `ork projetos esquecer <caminho>`.');
393
+ }
394
+ throw new ErroDeProjeto('projeto.desconhecido', casados.length ? `"${valor}" está registrado, mas o manifesto não está mais na raiz` : `"${valor}" não está no registro de projetos desta máquina`, registrados.map(candidatoDoRegistro), 'Veja os registrados com `ork projetos`; registre uma cópia com `ork projetos registrar <caminho>`.');
395
+ }
396
+ /**
397
+ * F2 do CHECK: o nome aponta a arvore principal; quem ja esta numa copia DESSE projeto (a worktree de
398
+ * uma thread) fica nela. Sem isso, `ORK_PROJETO` herdado numa worktree faria `ork docs verificar` ler
399
+ * a `main`. Caminho explicito nao passa por aqui: vale o caminho pedido.
400
+ */
401
+ function copiaDoCwd(raiz, cwd) {
402
+ const local = (0, util_1.subirAte)(path.resolve(cwd), manifest_1.NOME_MANIFESTO) ?? (0, util_1.subirAte)(path.resolve(cwd), manifest_1.NOME_MANIFESTO_LEGADO);
403
+ if (!local)
404
+ return raiz;
405
+ try {
406
+ return raizCanonica(local) === raiz ? fs.realpathSync(local) : raiz;
407
+ }
408
+ catch {
409
+ return raiz;
410
+ }
411
+ }
412
+ function resolverValor(valor, cwd) {
413
+ return ehCaminho(valor) ? raizPorCaminho(valor, cwd) : copiaDoCwd(raizPorNome(valor), cwd);
414
+ }
415
+ /** Os candidatos de um host sem cwd de projeto: o registro presente e o projeto do cwd, sem repetir. */
416
+ function candidatosDoHost(cwd) {
417
+ const saida = new Map();
418
+ for (const p of lerRegistroDeProjetos().projetos) {
419
+ if (manifestoPresente(p.raiz))
420
+ saida.set(p.raiz, candidatoDoRegistro(p));
421
+ }
422
+ const temManifesto = (0, util_1.subirAte)(path.resolve(cwd), manifest_1.NOME_MANIFESTO) ?? (0, util_1.subirAte)(path.resolve(cwd), manifest_1.NOME_MANIFESTO_LEGADO);
423
+ const local = temManifesto ? projetoDoCaminho(temManifesto) : null;
424
+ if (local && !saida.has(local.raiz)) {
425
+ const { raiz, carregado } = local;
426
+ saida.set(raiz, { nome: carregado.manifesto.project.name, abbrev: carregado.manifesto.project.abbrev,
427
+ raiz: raizParaExibir(raiz), remoto: remotoDoProjeto(raiz, carregado.manifesto.fabrica?.remoto ?? 'origin'), presente: true });
428
+ }
429
+ return [...saida.entries()].map(([raiz, candidato]) => ({ raiz, candidato }))
430
+ .sort((a, b) => a.candidato.nome.localeCompare(b.candidato.nome) || a.raiz.localeCompare(b.raiz));
431
+ }
432
+ /**
433
+ * RM-054 (fatia 2, D-G3): sem projeto pedido, a recusa oferece o panorama da rede, que le todos os
434
+ * projetos conhecidos sem escolher nenhum. A oferta sai do nucleo para valer em todo host, mesmo
435
+ * quando o modelo pula a instrucao da entrada dele.
436
+ */
437
+ exports.OFERTA_DA_REDE = 'Para ver todos os projetos e as threads de todas as máquinas sem escolher, com a fonte e a hora de cada parte: ' +
438
+ '`ork network roadmap` (tool ork_network_roadmap).';
439
+ /**
440
+ * O projeto-alvo do processo, ou `null` quando vale o cwd de sempre. Precedencia (D2):
441
+ * `--projeto` > `ORK_PROJETO` > host sem cwd (`ORK_PROJETO_EXPLICITO=1`, D3) > cwd.
442
+ */
443
+ function resolverProjetoAlvo(entrada = {}) {
444
+ const ambiente = entrada.ambiente ?? process.env, cwd = entrada.cwd ?? process.cwd();
445
+ const host = (ambiente[exports.ENV_PROJETO_EXPLICITO] ?? '').trim() === '1';
446
+ if (entrada.opcao !== undefined && entrada.opcao !== null) {
447
+ const valor = entrada.opcao.trim();
448
+ if (!valor) {
449
+ throw new ErroDeProjeto('projeto.desconhecido', '--projeto sem valor', lerRegistroDeProjetos().projetos.map(candidatoDoRegistro), 'Uso: --projeto <nome|caminho>; os nomes registrados estão em `ork projetos`.');
450
+ }
451
+ // D4 (F1 do CHECK): no host, quem pede escolhe entre os projetos da maquina pelo NOME. Caminho nao
452
+ // entra por nenhuma tool, nem pela que repassa os argumentos como dados (os scripts do Hermes).
453
+ if (host && ehCaminho(valor)) {
454
+ throw new ErroDeProjeto('projeto.desconhecido', `no host o projeto vem pelo nome registrado; o caminho "${valor}" não é aceito`, lerRegistroDeProjetos().projetos.map(candidatoDoRegistro), 'Use o nome de um projeto de `ork projetos` (ex.: --projeto orkastery).');
455
+ }
456
+ return { raiz: resolverValor(valor, cwd), origem: 'opcao', pedido: valor };
457
+ }
458
+ const doAmbiente = (ambiente[exports.ENV_PROJETO] ?? '').trim();
459
+ if (doAmbiente)
460
+ return { raiz: resolverValor(doAmbiente, cwd), origem: 'ambiente', pedido: doAmbiente };
461
+ if (host) {
462
+ const candidatos = candidatosDoHost(cwd);
463
+ if (candidatos.length === 1)
464
+ return { raiz: copiaDoCwd(candidatos[0].raiz, cwd), origem: 'unico-conhecido', pedido: null };
465
+ if (candidatos.length > 1) {
466
+ throw new ErroDeProjeto('projeto.escolha', `${candidatos.length} projetos conhecidos nesta máquina e nenhum foi pedido; o ork não escolhe pelo diretório do gateway`, candidatos.map((c) => c.candidato), `Repita com o projeto pedido: parâmetro \`projeto\` da tool, ou \`--projeto <nome>\`. ${exports.OFERTA_DA_REDE}`);
467
+ }
468
+ throw new ErroDeProjeto('projeto.nenhum', 'nenhum projeto conhecido nesta máquina e nenhum foi pedido', [], 'Registre o projeto com `ork projetos registrar <caminho>` (ou `ork init` na raiz dele) e repita com o nome. ' +
469
+ 'Sem clone nesta máquina, o roadmap de um projeto vem da forja: `ork network roadmap --projeto github:dono/repo` (tool ork_network_roadmap).');
470
+ }
471
+ return null;
472
+ }
473
+ // ---------------------------------------------------------------------------
474
+ // O alvo do processo.
475
+ // ---------------------------------------------------------------------------
476
+ let alvoDoProcesso = null;
477
+ /** Fixa o alvo do processo: as raizes padrao do manifesto passam a partir dele (`diretorioDoProjeto`). */
478
+ function fixarProjetoAlvo(alvo) {
479
+ alvoDoProcesso = alvo;
480
+ (0, manifest_1.fixarDiretorioDoProjeto)(alvo?.raiz ?? null);
481
+ }
482
+ function projetoAlvoAtual() {
483
+ return alvoDoProcesso;
484
+ }
485
+ /** D8: `~` no lugar da pasta da conta. O texto vai a canais de conversa. */
486
+ function raizParaExibir(raiz) {
487
+ const home = os.homedir();
488
+ if (raiz === home)
489
+ return '~';
490
+ return raiz.startsWith(home + path.sep) ? `~${path.sep}${raiz.slice(home.length + 1)}` : raiz;
491
+ }
492
+ /** Quantos OUTROS projetos presentes o registro desta maquina conhece. */
493
+ function outrosProjetosConhecidos(raiz) {
494
+ let canonica;
495
+ try {
496
+ canonica = raizCanonica(raiz);
497
+ }
498
+ catch {
499
+ canonica = path.resolve(raiz);
500
+ }
501
+ return listarProjetos().filter((p) => p.presente && p.raiz !== canonica).length;
502
+ }
503
+ /**
504
+ * As fontes que uma leitura pode deixar de fora, na mesma frase em todo comando e canal. A ponteira
505
+ * entre parenteses e o panorama da rede (RM-054, fatia 2), que le as tres com a fonte e a hora: o
506
+ * agente que chega pelo shell, e nao pela tool do host, le esta linha antes de responder.
507
+ */
508
+ exports.FORA_DA_CONSULTA = Object.freeze({
509
+ roadmap: 'roadmap (ork network roadmap)',
510
+ reservas: 'reservas do roadmap (ork network roadmap)',
511
+ outrasMaquinas: 'outras máquinas (ork network roadmap)',
512
+ threadsDaMaquina: 'threads deste projeto nesta máquina',
513
+ });
514
+ /** A lacuna de quem nao tem o remoto: nada foi lido de la, e isso nao e "nenhuma publicou". */
515
+ function semRemoto(remoto) {
516
+ return `outras máquinas: o projeto não tem o remoto ${remoto}, nada foi lido de ork/fabrica-estado`;
517
+ }
518
+ exports.ORIGENS_DO_PROJETO = Object.freeze({
519
+ opcao: 'pela opção --projeto',
520
+ ambiente: 'por ORK_PROJETO',
521
+ 'unico-conhecido': 'único projeto conhecido nesta máquina',
522
+ cwd: 'pelo diretório atual',
523
+ instalacao: 'fixado na instalação do servidor MCP',
524
+ });
525
+ /**
526
+ * Monta o cabecalho. `origem` explicita vence; sem ela, vale o alvo do processo quando ele e este
527
+ * projeto, e o cwd quando nao. `outrosProjetos: false` omite a contagem (o MCP e fixado num projeto
528
+ * e nao revela os demais).
529
+ */
530
+ function consultaDoProjeto(carregado, opcoes) {
531
+ const alvo = projetoAlvoAtual();
532
+ const origem = opcoes.origem ?? (alvo && path.resolve(alvo.raiz) === path.resolve(carregado.raiz) ? alvo.origem : 'cwd');
533
+ const outros = opcoes.outrosProjetos === false ? 0 : outrosProjetosConhecidos(carregado.raiz);
534
+ const remoto = opcoes.remoto !== undefined ? opcoes.remoto
535
+ : remotoDoProjeto(carregado.raiz, carregado.manifesto.fabrica?.remoto ?? 'origin');
536
+ return {
537
+ contrato: exports.CONTRATO_CONSULTA,
538
+ projeto: { nome: carregado.manifesto.project.name, abbrev: carregado.manifesto.project.abbrev,
539
+ raiz: raizParaExibir(carregado.raiz), remoto, origem },
540
+ lido: [...opcoes.lido],
541
+ naoLido: [...opcoes.naoLido, ...(outros > 0 ? [`outros projetos desta máquina: ${outros} (ork projetos)`] : [])],
542
+ };
543
+ }
544
+ /** As duas linhas do cabecalho em texto: o projeto consultado e o que nao foi lido. */
545
+ function linhasDaConsulta(c) {
546
+ const p = c.projeto;
547
+ return [
548
+ `Projeto consultado: ${p.nome} (${p.abbrev || '-'}) · ${p.raiz} · ${p.remoto ?? 'sem remoto'} · ${exports.ORIGENS_DO_PROJETO[p.origem]}`,
549
+ `Não lido: ${c.naoLido.length ? c.naoLido.join(' · ') : 'nada fora do projeto consultado'}`,
550
+ ];
551
+ }
package/dist/prompts.js CHANGED
@@ -257,7 +257,7 @@ Execute as fases deste bloco na ordem canonica, respeitando o escopo de cada fas
257
257
  Nao abra outra thread nem redespache a si mesmo. Se houver MCP do projeto, prefira suas ferramentas para estado e operacoes suportadas.
258
258
  No Claude com servidor \`orkastery\`, use nomes completos expostos: \`ToolSearch\` com \`select:mcp__orkastery__ork_thread_status,mcp__orkastery__ork_artifact_read\` descobre essas leituras; para outra ferramenta, selecione seu nome completo. Nao busque \`select:ork_thread_status\`. Outros hosts usam seu namespace descoberto.
259
259
  Quando disponiveis, use ork_artifact_read/write para objetivo, plano e parecer, e ork_claims_list/ork_claim_add para claims; nao escreva ledger nem estado canonico diretamente.
260
- Decisao que voce tomar sem perguntar ao dono vai ao ledger por \`ork decisao registrar <thread>\`, com o que foi decidido, o porque, como mudar, o custo de reverter agora e depois, o criterio escrito, quem decidiu e a evidencia; e assim que ela chega ao dono no resumo, em vez de sumir na conversa.
260
+ Decisao que voce tomar sem perguntar ao dono vai ao ledger por \`ork decisao registrar <thread>\` (no MCP, \`ork_decision_record\`, pois o sandbox do codex nao grava o ledger), com o que foi decidido, o porque, como mudar, o custo de reverter agora e depois, o criterio escrito, quem decidiu e a evidencia; e assim que ela chega ao dono no resumo, em vez de sumir na conversa.
261
261
  Artefato de autoria do agente nao e recibo oficial; cadastrar claim nao significa verifica-la.
262
262
  Para registrar produto, prefira ork_git_commit com HEAD atual e paths com claims; use ork_verify para verificacao real e ork_ship com HEADs fonte/destino conferidos para entrega. Commit local nao e SHIP; retorno incomplete exige reconciliar estado e remoto antes de repetir. Permissao para consultar nao concede essas operacoes: respeite a autorizacao nativa e os gates.
263
263
  Decisao do dono deve ser apresentada pela sessao condutora ao host; a sessao filha nao responde nem assume a identidade do dono.
@@ -72,6 +72,7 @@ const path = __importStar(require("node:path"));
72
72
  const node_crypto_1 = require("node:crypto");
73
73
  const estado_thread_1 = require("./estado-thread");
74
74
  const hitl_gates_1 = require("./hitl-gates");
75
+ const hitl_texto_livre_1 = require("./hitl-texto-livre");
75
76
  exports.CONTRATO_CONSENTIMENTO = 'ork.pulse-consent/v1';
76
77
  /**
77
78
  * Crockford base32 sem os quatro ambiguos (I, L, O, U). Com letra na frente e ao menos um digito,
@@ -140,7 +141,8 @@ function interpretarResposta(bruto, codigo) {
140
141
  return 'sim';
141
142
  if (['b', 'n', 'nao', 'não', 'agora nao', 'agora não'].includes(corpo))
142
143
  return 'nao';
143
- return undefined;
144
+ // RM-048 (D2): o mesmo vocabulario fechado do texto livre ("pode seguir", "aprovo", "depois").
145
+ return (0, hitl_texto_livre_1.consentimentoDoTrecho)((0, hitl_texto_livre_1.lerTrecho)(corpo));
144
146
  }
145
147
  /**
146
148
  * A assinatura de um conjunto de candidatos: os mesmos gates, a mesma oferta. O pedido aberto nao
@@ -234,7 +236,7 @@ function responderConsentimento(raiz, entrada) {
234
236
  }
235
237
  return { resposta: estado.respondido.resposta, repetida: true, pedido, respondido: estado.respondido };
236
238
  }
237
- if (Date.parse(quando) >= Date.parse(pedido.prazo)) {
239
+ if (Date.parse(quando) >= Date.parse(pedido.prazo) && entrada.aceitarVencido !== true) {
238
240
  throw new Error('consentimento do pulse: pedido vencido; nenhuma autorização concedida');
239
241
  }
240
242
  if (!resposta)
@@ -240,6 +240,11 @@ function varrerPulse(opcoes) {
240
240
  const consentimento = fila.candidatos.length ? (0, monitor_lock_1.comLockDaConversa)(dir, () => (0, pulse_consentimento_1.abrirConsentimento)(raiz, { quando,
241
241
  resumoSha256: assinaturaDoResumo(resumo), candidatos: fila.candidatos, prazoMin: Math.max(janelaMin, pulse_consentimento_1.PRAZO_PADRAO_MIN), estadoDir: dir })) : undefined;
242
242
  entregar((0, hitl_resumo_1.textoDoResumo)(resumo, { canal, codigo: consentimento?.codigo }), 'o resumo');
243
+ // RM-048 (D3): o resumo acabou de sair; a palavra solta ("sim") vale na janela curta dele.
244
+ try {
245
+ (0, pulse_resposta_1.atualizarEscuta)(raiz, quando, dir);
246
+ }
247
+ catch { /* dica de rota; a prova nao depende dela */ }
243
248
  // Marcado so depois do recibo do transporte: envio que falhou preserva a novidade.
244
249
  for (const item of itens)
245
250
  vistas[item.id] = assinaturaPulse(item);