@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,150 @@
1
+ "use strict";
2
+ /**
3
+ * RM-048 (item 2, D2 e D3): a resposta em linguagem natural, quando ela e inequivoca.
4
+ *
5
+ * Em 27/09 o dono respondeu "1. I-31. Aprovar, 2. D2 ..." e nada foi ao ledger: o ingresso so
6
+ * conhecia "P4EJ a" e "1a 2c", e a prosa caiu no assistente. A decisao do dono (28/09) foi: a
7
+ * resposta em linguagem natural registra quando for inequivoca ("aprovo", "sim", "pode seguir",
8
+ * "a", "1", "1. B, 2. A"); ambigua, volta como pergunta com as opcoes reais, nunca como veredito
9
+ * chutado; com mais de um pedido aberto na mesma thread, texto livre nao registra.
10
+ *
11
+ * "Inequivoca" nao e julgamento de ninguem. E esta regra, fechada e pura:
12
+ *
13
+ * 1. o texto tem a forma de uma resposta: letra a-d, digito 1-4, ou uma palavra do VOCABULARIO
14
+ * (que e uma lista, e nao um classificador), sozinha ou numerada ("1. B, 2. aprovo");
15
+ * 2. a palavra casa com UMA alternativa, pela acao dela ("aprovo" casa `aprovar`; "nao" num gate
16
+ * casa `recusar` e `esperar`, entao e ambigua e volta como pergunta);
17
+ * 3. a palavra sem numero e sem codigo so vale para a pergunta que acabou de sair, sozinha, e
18
+ * nunca para ato irreversivel (`JANELA_DO_TEXTO_LIVRE_MIN`).
19
+ *
20
+ * Este modulo nao autentica nada e nao registra nada. A prova continua sendo a do ingresso
21
+ * (`autenticarResposta`, no endereco do pulse), conferida ANTES de qualquer leitura de conteudo.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.FORMAS_DO_TEXTO_LIVRE = exports.TETO_DA_FORMA = exports.JANELA_DO_TEXTO_LIVRE_MIN = exports.ACOES_DA_INTENCAO = exports.VOCABULARIO_LIVRE = void 0;
25
+ exports.normalizarLivre = normalizarLivre;
26
+ exports.lerTrecho = lerTrecho;
27
+ exports.lerLista = lerLista;
28
+ exports.lerSolta = lerSolta;
29
+ exports.resolverTrecho = resolverTrecho;
30
+ exports.consentimentoDoTrecho = consentimentoDoTrecho;
31
+ /**
32
+ * O vocabulario fechado, ja normalizado (minusculas, sem acento). Palavra fora daqui nao e
33
+ * resposta: vai ao assistente, como sempre foi. Acrescentar uma palavra e decisao de contrato,
34
+ * com teste, e nunca efeito colateral de alguem achar que "entendeu" o dono.
35
+ */
36
+ exports.VOCABULARIO_LIVRE = Object.freeze({
37
+ afirmar: ['sim', 's', 'ok', 'pode', 'pode seguir', 'pode ir', 'segue', 'siga', 'seguir', 'manda', 'aprovo', 'aprovado',
38
+ 'aprovada', 'aprovar', 'aprova', 'confirmo', 'confirmado', 'de acordo'],
39
+ negar: ['nao', 'n'],
40
+ revisar: ['recuso', 'recusado', 'recusar', 'reprovo', 'reprovado', 'revisar', 'revisao', 'refazer', 'volta', 'voltar'],
41
+ esperar: ['esperar', 'espera', 'aguardar', 'aguarda', 'aguarde', 'depois', 'mais tarde', 'agora nao'],
42
+ detalhe: ['detalhe', 'detalhes', 'evidencia', 'evidencias'],
43
+ });
44
+ /** Que acoes de alternativa cada intencao casa. `negar` casa duas de proposito: e ambigua num gate. */
45
+ exports.ACOES_DA_INTENCAO = Object.freeze({
46
+ afirmar: ['aprovar', 'responder'],
47
+ negar: ['recusar', 'esperar'],
48
+ revisar: ['recusar'],
49
+ esperar: ['esperar'],
50
+ });
51
+ /**
52
+ * D3: a palavra solta (sem numero e sem codigo) so vale ate esta quantidade de minutos depois de
53
+ * a pergunta sair. Numero e codigo nao tem janela: a linha que o dono recebeu vale enquanto o gate
54
+ * esperar a mesma pergunta.
55
+ */
56
+ exports.JANELA_DO_TEXTO_LIVRE_MIN = 60;
57
+ /**
58
+ * As palavras em fonte de expressao regular, com as variantes de acento. E daqui que saem as
59
+ * formas `lista` e `livre` de `GRAMATICA_DO_PULSE`, que os adaptadores copiam texto a texto.
60
+ */
61
+ const PALAVRAS_DA_FORMA = 'pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|' +
62
+ 'confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|' +
63
+ 'volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?';
64
+ /** Uma resposta: letra, ou palavra do vocabulario. */
65
+ const RESPOSTA_DA_FORMA = `(?:[a-d]|${PALAVRAS_DA_FORMA})`;
66
+ /**
67
+ * Um item numerado: "1a", "1 a", "1. B", "1) aprovo", "1 - nao", "1: detalhes".
68
+ *
69
+ * Sem dois quantificadores de espaco lado a lado: o adaptador testa a forma ANTES de conferir a
70
+ * allowlist, e forma com backtracking caro seria uma porta de negacao de servico para quem nao e
71
+ * o dono. Os adaptadores tambem recusam, antes da regex, texto acima de `TETO_DA_FORMA`.
72
+ */
73
+ const ITEM_DA_FORMA = `[0-9]{1,2}(?:[ \\t]*[.):-])?[ \\t]*${RESPOSTA_DA_FORMA}`;
74
+ /** Maior texto que o ingresso testa contra as formas do pulse. */
75
+ exports.TETO_DA_FORMA = 300;
76
+ /** As fontes das duas formas novas do ingresso. Ambas sao lidas sem diferenca de caixa. */
77
+ exports.FORMAS_DO_TEXTO_LIVRE = Object.freeze({
78
+ /** Resposta numerada, uma ou varias, separadas por virgula, ponto e virgula, espaco ou linha. */
79
+ lista: `^[ \\t]*${ITEM_DA_FORMA}(?:[ \\t\\r\\n,;]*${ITEM_DA_FORMA})*[ \\t]*(?:[.!][ \\t]*)?$`,
80
+ /** Resposta solta: letra, digito ou palavra, sozinha na mensagem. */
81
+ livre: `^[ \\t]*(?:[1-4]|${RESPOSTA_DA_FORMA})[ \\t]*(?:[.!]+[ \\t]*)?$`,
82
+ });
83
+ /** Minusculas, sem acento, espaco unico, sem pontuacao final. */
84
+ function normalizarLivre(texto) {
85
+ return texto.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/\s+/g, ' ').trim().replace(/[.!]+$/, '').trim();
86
+ }
87
+ /** O que um pedaco de resposta quer dizer, ou `null` quando ele nao e resposta. */
88
+ function lerTrecho(bruto) {
89
+ const t = normalizarLivre(bruto);
90
+ if (/^[a-d]$/.test(t))
91
+ return { tipo: 'letra', letra: t };
92
+ if (/^[1-4]$/.test(t))
93
+ return { tipo: 'digito', numero: Number(t) };
94
+ for (const [intencao, palavras] of Object.entries(exports.VOCABULARIO_LIVRE)) {
95
+ if (palavras.includes(t))
96
+ return { tipo: 'intencao', intencao, palavra: t };
97
+ }
98
+ return null;
99
+ }
100
+ /** Os itens de uma resposta numerada, na ordem em que o dono escreveu. */
101
+ function lerLista(texto) {
102
+ if (texto.length > exports.TETO_DA_FORMA || !new RegExp(exports.FORMAS_DO_TEXTO_LIVRE.lista, 'i').test(texto))
103
+ return null;
104
+ const itens = [];
105
+ const item = new RegExp(`([0-9]{1,2})(?:[ \\t]*[.):-])?[ \\t]*(${RESPOSTA_DA_FORMA})(?=[ \\t\\r\\n,;]*(?:[0-9]|[.!]?[ \\t]*$))`, 'gi');
106
+ for (const m of texto.matchAll(item)) {
107
+ const trecho = lerTrecho(m[2]);
108
+ if (!trecho)
109
+ return null;
110
+ itens.push({ numero: Number(m[1]), trecho });
111
+ }
112
+ return itens.length ? itens : null;
113
+ }
114
+ /** A resposta solta, ou `null` quando o texto nao tem a forma de uma. */
115
+ function lerSolta(texto) {
116
+ return texto.length <= exports.TETO_DA_FORMA && new RegExp(exports.FORMAS_DO_TEXTO_LIVRE.livre, 'i').test(texto) ? lerTrecho(texto) : null;
117
+ }
118
+ /**
119
+ * Qual alternativa o trecho escolhe, pelas acoes das alternativas (na ordem do pedido). Letra e
120
+ * digito escolhem pela posicao; palavra, pela acao, e so quando exatamente uma casa.
121
+ */
122
+ function resolverTrecho(trecho, acoes, letras) {
123
+ if (trecho.tipo === 'letra') {
124
+ const i = letras.indexOf(trecho.letra);
125
+ return i >= 0 ? { tipo: 'escolha', indice: i } : { tipo: 'nenhuma' };
126
+ }
127
+ if (trecho.tipo === 'digito')
128
+ return trecho.numero <= acoes.length ? { tipo: 'escolha', indice: trecho.numero - 1 } : { tipo: 'nenhuma' };
129
+ const intencao = trecho.intencao;
130
+ if (intencao === 'detalhe')
131
+ return { tipo: 'detalhe' };
132
+ const casam = acoes.map((a, i) => exports.ACOES_DA_INTENCAO[intencao].includes(a) ? i : -1).filter(i => i >= 0);
133
+ if (casam.length === 1)
134
+ return { tipo: 'escolha', indice: casam[0] };
135
+ return casam.length ? { tipo: 'ambigua', indices: casam } : { tipo: 'nenhuma' };
136
+ }
137
+ /** O que o dono respondeu ao resumo ("posso mandar as perguntas?"), pela mesma regra. */
138
+ function consentimentoDoTrecho(trecho) {
139
+ if (!trecho)
140
+ return undefined;
141
+ if (trecho.tipo === 'letra')
142
+ return trecho.letra === 'a' ? 'sim' : trecho.letra === 'b' ? 'nao' : undefined;
143
+ if (trecho.tipo === 'digito')
144
+ return trecho.numero === 1 ? 'sim' : trecho.numero === 2 ? 'nao' : undefined;
145
+ if (trecho.intencao === 'afirmar')
146
+ return 'sim';
147
+ if (trecho.intencao === 'negar' || trecho.intencao === 'esperar')
148
+ return 'nao';
149
+ return undefined;
150
+ }
package/dist/hosts.js CHANGED
@@ -59,6 +59,7 @@ exports.lerRecibo = lerRecibo;
59
59
  exports.classificarDivergencia = classificarDivergencia;
60
60
  exports.renderizarArquivo = renderizarArquivo;
61
61
  exports.instalarAdaptador = instalarAdaptador;
62
+ exports.desinstalarExperiencia = desinstalarExperiencia;
62
63
  exports.tabelaDeHosts = tabelaDeHosts;
63
64
  exports.textoDosPitfalls = textoDosPitfalls;
64
65
  exports.textoDaInstalacao = textoDaInstalacao;
@@ -78,6 +79,9 @@ const path = __importStar(require("node:path"));
78
79
  const catalogo_1 = require("./catalogo");
79
80
  const util_1 = require("./util");
80
81
  const versao_1 = require("./versao");
82
+ const manifest_1 = require("./manifest");
83
+ const experiencia_1 = require("./experiencia");
84
+ const experiencia_instalacao_1 = require("./experiencia-instalacao");
81
85
  exports.HOSTS = {
82
86
  'claude-code': {
83
87
  host: 'claude-code',
@@ -122,7 +126,7 @@ exports.HOSTS = {
122
126
  },
123
127
  {
124
128
  titulo: 'skill confundida com autorizacao de runtime',
125
- detalhe: 'instalar instrucoes nao habilita permissoes, hooks ou sandbox nem altera AGENTS.md; gates e preflight continuam no nucleo, e descoberta nao prova a jornada conversacional',
129
+ detalhe: 'instalar instrucoes nao habilita permissoes, hooks ou sandbox; em projeto configurado, AGENTS.md recebe apenas o bloco reversível da experiência. Gates e preflight continuam no nucleo, e descoberta nao prova a jornada conversacional',
126
130
  prova: 'ork doctor',
127
131
  },
128
132
  ],
@@ -282,6 +286,64 @@ function instalarAdaptador(host, opcoes = {}) {
282
286
  fontes.push({ origem: ref, relativo: path.posix.join('references', path.basename(ref)) });
283
287
  }
284
288
  }
289
+ if (host === 'hermes') {
290
+ for (const skill of (0, catalogo_1.skillsDoCatalogo)(catalogo).filter(s => ['orchestration-experience', 'orchestration-experience-pt-br'].includes(s.nome))) {
291
+ fontes.push({ origem: skill.caminho, relativo: path.posix.join('skills', skill.nome, 'SKILL.md') });
292
+ }
293
+ }
294
+ // A experiência é opcional: manifesto com erro, bloco alheio ou caminho inadequado pulam só a
295
+ // ativação, com aviso, e o adaptador segue instalado como antes da RM-051.
296
+ const segue = 'O restante da instalação do adaptador segue normalmente.';
297
+ const carregado = (0, manifest_1.carregarManifesto)(projeto);
298
+ let avisoExperiencia;
299
+ if (carregado?.raiz === projeto && carregado.erros.length) {
300
+ avisoExperiencia = `Pacote de experiência pulado: o manifesto tem erros (${carregado.erros[0]}). ${segue}`;
301
+ }
302
+ // Preferência inválida (ex.: experience: "false") não ativa nem remove nada: quem escreveu pode ter
303
+ // tentado desligar o pacote. Corrigir com ork onboarding set maestro volta a instalar.
304
+ const invalida = carregado?.raiz === projeto ? carregado.avisos.find(a => a.startsWith('experiencia.config.invalid')) : undefined;
305
+ if (!avisoExperiencia && invalida)
306
+ avisoExperiencia = `Pacote de experiência pulado: ${invalida}. ${segue}`;
307
+ const preferencias = carregado?.raiz === projeto && !avisoExperiencia ? (0, experiencia_1.resolverExperiencia)(carregado.manifesto.owner) : null;
308
+ const hostComBloco = host === 'codex' || host === 'claude-code';
309
+ // O bloco aponta o diretório relativo ao projeto: CLAUDE.md e AGENTS.md costumam ir ao repositório.
310
+ const diretorioExperiencia = path.relative(caminhoReal(projeto), caminhoReal(path.join(destino, 'skills', 'core')))
311
+ .split(path.sep).join('/');
312
+ if (preferencias?.experience && host !== 'openclaw') {
313
+ const skillRelativa = host === 'hermes' ? `skills/${preferencias.skill}/SKILL.md`
314
+ : `skills/core/${preferencias.skill}/SKILL.md`;
315
+ if (!fontes.some(f => f.relativo === skillRelativa)) {
316
+ avisoExperiencia = `Pacote de experiência pulado: a skill ${preferencias.skill} não está no catálogo. ${segue}`;
317
+ }
318
+ else if (hostComBloco && (diretorioExperiencia.startsWith('../') || path.isAbsolute(diretorioExperiencia))) {
319
+ avisoExperiencia = `Pacote de experiência pulado: o adaptador fica fora do projeto e o bloco de instruções só aponta caminhos do projeto. ${segue}`;
320
+ }
321
+ else if (hostComBloco && /[\r\n`]/.test(diretorioExperiencia)) {
322
+ avisoExperiencia = `Pacote de experiência pulado: o caminho das skills contém quebra de linha ou crase, incompatível com o bloco de instruções. ${segue}`;
323
+ }
324
+ }
325
+ let planoExperiencia = null;
326
+ if (preferencias && hostComBloco && !avisoExperiencia) {
327
+ try {
328
+ planoExperiencia = (0, experiencia_instalacao_1.planejarExperiencia)(projeto, host, preferencias.experience ? diretorioExperiencia : null);
329
+ }
330
+ catch (e) {
331
+ const motivo = e.message;
332
+ if (!/^experiencia\.(bloco\.conflict|path\.unsafe)/.test(motivo))
333
+ throw e;
334
+ avisoExperiencia = `Pacote de experiência pulado: ${motivo.startsWith('experiencia.path') ? 'o arquivo de instruções é link ou não é arquivo comum' : `o bloco de instruções ou o recibo .orkastery/experiencia/${host}.json foi editado ou está duplicado`}; nada foi escrito. Revise os dois à mão. ${segue}`;
335
+ }
336
+ }
337
+ // O preflight cobre o destino e os arquivos do próprio adaptador antes de copiar o primeiro. Links
338
+ // acima do destino (.agents/skills compartilhado, diretório do usuário) são escolha de quem instala.
339
+ for (const alvo of [...fontes.map(f => path.join(destino, f.relativo)), path.join(destino, 'INSTALADO.json')]) {
340
+ for (let atual = alvo; atual !== path.dirname(destino) && atual !== path.dirname(atual); atual = path.dirname(atual)) {
341
+ const stat = fs.lstatSync(atual, { throwIfNoEntry: false });
342
+ if (stat && (stat.isSymbolicLink() || (atual === alvo ? !stat.isFile() || stat.nlink !== 1 : !stat.isDirectory()))) {
343
+ throw Error('experiencia.path.unsafe: destino do adaptador');
344
+ }
345
+ }
346
+ }
285
347
  // I-43 (D5): o recibo e lido UMA vez, antes de classificar, e e ele que transforma
286
348
  // "divergente" em um dos quatro motivos.
287
349
  const recibo = lerRecibo(destino);
@@ -363,6 +425,9 @@ function instalarAdaptador(host, opcoes = {}) {
363
425
  ok: !barrado,
364
426
  orkBin,
365
427
  pitfalls: def.pitfalls,
428
+ ...(preferencias || avisoExperiencia ? { experiencia: { ativa: host !== 'openclaw' && !!preferencias?.experience && !avisoExperiencia,
429
+ arquivos: planoExperiencia?.mudancas.map(m => path.relative(planoExperiencia.projeto, m.arquivo)) ?? [], skill: preferencias?.skill ?? '',
430
+ ...(avisoExperiencia ? { aviso: avisoExperiencia } : {}) } } : {}),
366
431
  };
367
432
  if (opcoes.dryRun === true || barrado)
368
433
  return resultado;
@@ -398,8 +463,24 @@ function instalarAdaptador(host, opcoes = {}) {
398
463
  bytes: a.bytes,
399
464
  })),
400
465
  }, null, 2) + '\n', 'utf8');
466
+ if (planoExperiencia)
467
+ (0, experiencia_instalacao_1.aplicarExperiencia)(planoExperiencia);
401
468
  return resultado;
402
469
  }
470
+ /** Caminho real do maior ancestral existente, com o resto como está: o destino pode não existir ainda. */
471
+ function caminhoReal(alvo) {
472
+ let existente = path.resolve(alvo);
473
+ while (!fs.existsSync(existente) && existente !== path.dirname(existente))
474
+ existente = path.dirname(existente);
475
+ return path.join(fs.realpathSync(existente), path.relative(existente, path.resolve(alvo)));
476
+ }
477
+ /** Remove somente o bloco do pacote; o adaptador e as demais instruções permanecem. */
478
+ function desinstalarExperiencia(projeto, host, dryRun = false) {
479
+ const plano = (0, experiencia_instalacao_1.planejarExperiencia)(projeto, host, null);
480
+ if (!dryRun)
481
+ (0, experiencia_instalacao_1.aplicarExperiencia)(plano);
482
+ return { host, dryRun, arquivos: plano.mudancas.map(m => path.relative(plano.projeto, m.arquivo)) };
483
+ }
403
484
  /** Texto de `ork adapter list`. */
404
485
  function tabelaDeHosts() {
405
486
  const linhas = ['Adaptadores de host (Camada 1, o podio)', ''];
@@ -429,6 +510,10 @@ function textoDosPitfalls(host) {
429
510
  /** Texto de `ork adapter install`. */
430
511
  function textoDaInstalacao(r) {
431
512
  const linhas = [];
513
+ if (r.experiencia)
514
+ linhas.push(`Experiência: ${r.experiencia.ativa ? 'ativação preparada' : 'desativada ou sem integração de skills'}${r.experiencia.skill ? ` (${r.experiencia.skill})` : ''}.`);
515
+ if (r.experiencia?.aviso)
516
+ linhas.push(`Aviso: ${r.experiencia.aviso}`);
432
517
  linhas.push(r.dryRun
433
518
  ? `Simulacao (--dry-run) da instalacao do adaptador ${r.host}: nada foi escrito.`
434
519
  : r.ok