@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.
- package/adapters/README.md +11 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
- package/adapters/claude-code/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +2 -2
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/master.md +11 -8
- package/adapters/claude-code/commands/ork.md +8 -0
- package/adapters/claude-code/commands/ship.md +2 -2
- package/adapters/codex/skills/ork/SKILL.md +12 -1
- package/adapters/hermes/README.md +3 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
- package/adapters/hermes/bin/ork-brain.sh +3 -0
- package/adapters/hermes/bin/ork-maestro.sh +4 -1
- package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
- package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
- package/adapters/hermes/hermes.plugin.json +2 -0
- package/adapters/hermes/hitl-ingress/__init__.py +36 -3
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
- package/adapters/openclaw/README.md +34 -3
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +92 -39
- package/adapters/openclaw/openclaw.plugin.json +10 -3
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +32 -3
- package/adapters/openclaw/src/index.ts +108 -41
- package/assets/docs/padroes/documentacao-de-produto.md +2 -2
- package/assets/docs/padroes/roadmap-de-produto.md +1 -1
- package/assets/docs/produto/_modelo-feature.md +2 -0
- package/assets/orkmind_bridge.py +214 -1
- package/dist/adapters/claude-bg.js +88 -9
- package/dist/adapters/codex-events.js +3 -1
- package/dist/board.js +74 -0
- package/dist/branch-de-estado.js +5 -4
- package/dist/busca-semantica.js +161 -0
- package/dist/ci.js +141 -10
- package/dist/company-brain-cli.js +27 -2
- package/dist/company-brain-context.js +132 -0
- package/dist/company-brain-dossie.js +376 -0
- package/dist/company-brain-mcp.js +4 -1
- package/dist/conducao.js +117 -31
- package/dist/decisao-autonoma.js +42 -0
- package/dist/docs.js +64 -22
- package/dist/doctor.js +33 -0
- package/dist/entrega-pr.js +109 -5
- package/dist/experiencia-instalacao.js +256 -0
- package/dist/experiencia.js +72 -0
- package/dist/fabrica-estado.js +43 -10
- package/dist/fechamento.js +105 -0
- package/dist/fix.js +1 -1
- package/dist/forja.js +435 -0
- package/dist/gates.js +10 -1
- package/dist/hitl-classificacao.js +11 -1
- package/dist/hitl-contract.js +50 -7
- package/dist/hitl-curto.js +158 -0
- package/dist/hitl-gates.js +67 -1
- package/dist/hitl-lote.js +30 -15
- package/dist/hitl-presentation.js +10 -7
- package/dist/hitl-resumo.js +33 -12
- package/dist/hitl-texto-livre.js +150 -0
- package/dist/hosts.js +86 -1
- package/dist/index.js +605 -41
- package/dist/indice-vetorial.js +317 -0
- package/dist/init.js +13 -0
- package/dist/integracoes-locais.js +1 -1
- package/dist/intelligence-benchmark-contract.js +570 -0
- package/dist/intelligence-graph-cli.js +430 -0
- package/dist/intelligence-graph-contract.js +577 -0
- package/dist/intelligence-graph-extract-md.js +469 -0
- package/dist/intelligence-graph-extract-ts.js +810 -0
- package/dist/intelligence-graph-extract.js +369 -0
- package/dist/intelligence-graph-index.js +512 -0
- package/dist/intelligence-graph-parsers.js +327 -0
- package/dist/intelligence-graph-query.js +344 -0
- package/dist/intelligence-graph-repo.js +187 -0
- package/dist/leases.js +84 -0
- package/dist/ledger.js +14 -0
- package/dist/maestro-cli.js +10 -1
- package/dist/maestro-contract.js +5 -1
- package/dist/maestro-discovery.js +16 -2
- package/dist/maestro-snapshot.js +6 -2
- package/dist/manifest.js +142 -5
- package/dist/master-batch.js +3 -2
- package/dist/master-digest.js +2 -2
- package/dist/master-nota.js +203 -0
- package/dist/master.js +4 -0
- package/dist/mcp-experiencia.js +164 -0
- package/dist/mcp-git.js +36 -10
- package/dist/mcp-install.js +3 -1
- package/dist/mcp-maestro.js +3 -2
- package/dist/mcp-server.js +84 -5
- package/dist/memoria.js +147 -1
- package/dist/network-roadmap.js +907 -0
- package/dist/ocupacao.js +38 -2
- package/dist/onboarding.js +113 -7
- package/dist/orkmind.js +165 -9
- package/dist/phase.js +166 -13
- package/dist/policies.js +2 -0
- package/dist/projeto-alvo.js +551 -0
- package/dist/prompts.js +1 -1
- package/dist/pulse-consentimento.js +4 -2
- package/dist/pulse-delivery.js +5 -0
- package/dist/pulse-resposta.js +674 -49
- package/dist/pulse.js +21 -0
- package/dist/ratelimit.js +3 -2
- package/dist/retry.js +230 -9
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +213 -0
- package/dist/runtime-ambiente.js +12 -1
- package/dist/session-watcher-claude.js +26 -0
- package/dist/sessoes.js +49 -15
- package/dist/thread-close.js +3 -0
- package/dist/verify.js +5 -4
- package/dist/worktree.js +77 -0
- package/eval/casos/orchestration-experience-pt-br.json +48 -0
- package/eval/casos/orchestration-experience.json +48 -0
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +581 -0
- package/schemas/graph-benchmark.v1.schema.json +2240 -0
- package/schemas/maestro-snapshot.schema.json +23 -0
- package/skills/README.md +7 -5
- package/skills/core/onboarding/SKILL.md +7 -0
- package/skills/core/orchestration-experience/SKILL.md +113 -0
- package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/go-implementation/SKILL.md +2 -0
package/dist/ocupacao.js
CHANGED
|
@@ -25,6 +25,7 @@ exports.EVENTOS_QUE_DESTRAVAM = exports.PAUSA_PREVISTA = void 0;
|
|
|
25
25
|
exports.ehAprovacaoHumana = ehAprovacaoHumana;
|
|
26
26
|
exports.ehPausaPrevista = ehPausaPrevista;
|
|
27
27
|
exports.faseTemPausaPrevista = faseTemPausaPrevista;
|
|
28
|
+
exports.sessaoLivreDaVaga = sessaoLivreDaVaga;
|
|
28
29
|
exports.avaliarOcupacao = avaliarOcupacao;
|
|
29
30
|
const ledger_1 = require("./ledger");
|
|
30
31
|
const sessoes_adopt_1 = require("./sessoes-adopt");
|
|
@@ -70,15 +71,50 @@ function ultimaSessao(thread, eventos) {
|
|
|
70
71
|
/**
|
|
71
72
|
* Carimbo do ultimo sinal de vida: o evento mais novo do ledger que seja TRABALHO da
|
|
72
73
|
* thread. `slot_released` fica de fora: e contabilidade do escalonador, e se contasse
|
|
73
|
-
* como atividade a thread stale rejuvenesceria no exato momento em que e expulsa.
|
|
74
|
+
* como atividade a thread stale rejuvenesceria no exato momento em que e expulsa. O
|
|
75
|
+
* `slot_refused` (RM-037) tambem: despacho recusado por vaga nao e trabalho.
|
|
74
76
|
*/
|
|
75
77
|
function ultimaAtividadeEm(eventos) {
|
|
76
78
|
for (let i = eventos.length - 1; i >= 0; i--) {
|
|
77
|
-
|
|
79
|
+
const t = eventos[i].tipo;
|
|
80
|
+
if (t !== ledger_1.TIPOS_DE_EVENTO.vagaLiberada && t !== ledger_1.TIPOS_DE_EVENTO.vagaRecusada)
|
|
78
81
|
return eventos[i].ts;
|
|
79
82
|
}
|
|
80
83
|
return null;
|
|
81
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* RM-037 (rm037defeito, defeito 3; achado N1 da rodada 2 do CHECK): a sessao com conducao VIVA que mesmo
|
|
87
|
+
* assim nao ocupa vaga do despacho. Sao tres: a parada (sem trabalho no ledger ha mais que `staleMin`,
|
|
88
|
+
* o mesmo teto do escalonador) e a que escalou para o humano depois de comecar (`gate_blocked`
|
|
89
|
+
* `human.pending` sem destravamento, ou sessao bloqueada no runtime), e a que o pulse declarou em silencio
|
|
90
|
+
* (`runtime.silencio`). A pausa PREVISTA ao fim do bloco nao entra: ela e gravada no proprio
|
|
91
|
+
* despacho e a sessao roda ate o fim do bloco. O impedimento de verify tambem nao: a sessao corrige e
|
|
92
|
+
* verifica de novo. Somente leitura.
|
|
93
|
+
*/
|
|
94
|
+
function sessaoLivreDaVaga(eventos, desde, agora, staleMin,
|
|
95
|
+
/** A-3 do CHECK 4: o bloqueio no runtime e por sessao; o de outra sessao (a sucedida) nao libera esta. */
|
|
96
|
+
sessionId) {
|
|
97
|
+
const ultima = ultimaAtividadeEm(eventos);
|
|
98
|
+
if (ultima !== null && minutos(ultima, agora) >= staleMin)
|
|
99
|
+
return 'stale';
|
|
100
|
+
const inicio = Date.parse(desde);
|
|
101
|
+
const depois = eventos.filter((e) => Date.parse(e.ts) >= inicio);
|
|
102
|
+
// A-2 e S-7 do CHECK 3: o silencio que o pulse declarou (`runtime.silencio`) e a sessao bloqueada no runtime
|
|
103
|
+
// esperando o dono tambem nao executam; os dois saem da conta ate o evento que os resolve.
|
|
104
|
+
for (let i = depois.length - 1; i >= 0; i--) {
|
|
105
|
+
const e = depois[i];
|
|
106
|
+
if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.gateBloqueado || (e.motivo !== 'human.pending' && e.motivo !== 'runtime.silencio'))
|
|
107
|
+
continue;
|
|
108
|
+
const resolvida = depois.slice(i + 1).some((p) => exports.EVENTOS_QUE_DESTRAVAM.includes(p.tipo) || ehAprovacaoHumana(p));
|
|
109
|
+
if (!resolvida)
|
|
110
|
+
return e.motivo;
|
|
111
|
+
}
|
|
112
|
+
const bloqueio = depois.filter((e) => (e.tipo === 'sessao_bloqueada' || e.tipo === 'sessao_destravada') &&
|
|
113
|
+
(!sessionId || e.sessionId === sessionId)).at(-1);
|
|
114
|
+
if (bloqueio?.tipo === 'sessao_bloqueada')
|
|
115
|
+
return 'human.pending';
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
82
118
|
/** A thread tem pausa humana ABERTA? Mesmas tres fontes do monitor, resumidas. */
|
|
83
119
|
function pausaHumanaAberta(thread, eventos) {
|
|
84
120
|
if (thread.status === 'pausada' && faseTemPausaPrevista(thread)) {
|
package/dist/onboarding.js
CHANGED
|
@@ -51,9 +51,11 @@ const project_state_1 = require("./project-state");
|
|
|
51
51
|
const ledger_1 = require("./ledger");
|
|
52
52
|
const policies_1 = require("./policies");
|
|
53
53
|
const horario_1 = require("./horario");
|
|
54
|
+
const experiencia_1 = require("./experiencia");
|
|
55
|
+
const yaml_1 = require("./yaml");
|
|
54
56
|
exports.CONTRATO_ONBOARDING = 'ork.onboarding/v1';
|
|
55
57
|
exports.PAUTA_ONBOARDING = [
|
|
56
|
-
{ etapa: 'maestro', pergunta: `Quem conduz o projeto, quais são seus objetivos e preferências, e em qual fuso horário o Orkastery deve mostrar horários? Informe o fuso como nome IANA, ex.: {"fuso":"${horario_1.FUSO_DE_BRASILIA}"}; ele orienta ${horario_1.CHAVE_DO_FUSO} no manifesto, sem editá-lo.` },
|
|
58
|
+
{ etapa: 'maestro', pergunta: `Quem conduz o projeto, quais são seus objetivos e preferências, e em qual fuso horário o Orkastery deve mostrar horários? Informe o fuso como nome IANA, ex.: {"fuso":"${horario_1.FUSO_DE_BRASILIA}"}; ele orienta ${horario_1.CHAVE_DO_FUSO} no manifesto, sem editá-lo. Preferências em {"owner":{...}} gravam owner no manifesto.` },
|
|
57
59
|
{ etapa: 'credenciais', pergunta: 'Quais provedores públicos e nomes de variáveis em env serão usados? Segredos somente em ~/.hermes/.env; nunca informe valores.' },
|
|
58
60
|
{ etapa: 'bancos', pergunta: 'Quais bancos públicos e nomes de variáveis em env configuram as conexões? Nunca informe DSN ou senha; valores somente em ~/.hermes/.env.' },
|
|
59
61
|
{ etapa: 'memoria', pergunta: 'Deseja OrkMind? Informe {"modo":"orkmind"} ou {"modo":"files"}; a escolha orienta memory.mode no manifesto, sem editá-lo.' },
|
|
@@ -118,6 +120,16 @@ function validarConteudo(etapa, conteudo) {
|
|
|
118
120
|
// I-35: o fuso do dono, quando informado, precisa ser um nome IANA que o Intl aceita.
|
|
119
121
|
if (etapa === 'maestro' && objeto(conteudo) && Object.hasOwn(conteudo, 'fuso') && (0, horario_1.normalizarFuso)(conteudo.fuso) === undefined)
|
|
120
122
|
invalido();
|
|
123
|
+
if (etapa === 'maestro' && objeto(conteudo) && Object.hasOwn(conteudo, 'owner')) {
|
|
124
|
+
if (!objeto(conteudo.owner) || Object.keys(conteudo.owner).some(k => !['language', 'timezone', 'depth', 'experience'].includes(k)))
|
|
125
|
+
invalido();
|
|
126
|
+
try {
|
|
127
|
+
(0, experiencia_1.validarPreferencias)(conteudo.owner);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
invalido();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
121
133
|
// Etapas sensíveis usam um formato fechado: não há espaço para texto de credenciais.
|
|
122
134
|
if (etapa === 'credenciais' || etapa === 'bancos') {
|
|
123
135
|
if (!objeto(conteudo))
|
|
@@ -263,18 +275,109 @@ function comTrava(raiz, alterar) {
|
|
|
263
275
|
fs.unlinkSync(trava);
|
|
264
276
|
}
|
|
265
277
|
}
|
|
278
|
+
/** Comentário depois de valor simples ou entre aspas completas; `#` dentro das aspas é valor. */
|
|
279
|
+
const COMENTARIO_NA_LINHA = /^\s*[^:#]+:\s*(?:"(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|[^"'#\s][^#]*?)?(\s+#.*)$/;
|
|
280
|
+
/** Altera somente as chaves públicas solicitadas, preservando comentários e outras seções. */
|
|
281
|
+
function prepararPreferencias(raiz, conteudo) {
|
|
282
|
+
if (!objeto(conteudo) || !objeto(conteudo.owner) || !Object.keys(conteudo.owner).length)
|
|
283
|
+
return null;
|
|
284
|
+
const c = (0, manifest_1.exigirManifesto)((0, project_state_1.projectState)(raiz).root), arquivo = c.caminho;
|
|
285
|
+
const stat = fs.lstatSync(arquivo);
|
|
286
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
287
|
+
throw Error('experiencia.manifesto.unsafe');
|
|
288
|
+
const anterior = fs.readFileSync(arquivo, 'utf8'), eol = anterior.includes('\r\n') ? '\r\n' : '\n';
|
|
289
|
+
const linhas = anterior.split(eol), ocorrencias = linhas.map((l, i) => /^(?:owner|"owner"|'owner')\s*:/.test(l) ? i : -1).filter(i => i >= 0);
|
|
290
|
+
if (ocorrencias.length > 1)
|
|
291
|
+
throw Error('experiencia.manifesto.conflict: owner duplicado');
|
|
292
|
+
let inicio = ocorrencias[0];
|
|
293
|
+
// Seção nova antes da quebra final, sem linha em branco sobrando nem perda do newline.
|
|
294
|
+
if (inicio === undefined) {
|
|
295
|
+
inicio = linhas.at(-1) === '' ? linhas.length - 1 : linhas.length;
|
|
296
|
+
linhas.splice(inicio, 0, 'owner:');
|
|
297
|
+
}
|
|
298
|
+
if (!/^(?:owner|"owner"|'owner')\s*:\s*(?:\{\}\s*)?(?:#.*)?$/.test(linhas[inicio]))
|
|
299
|
+
throw Error('experiencia.manifesto.conflict: owner não é mapa em bloco');
|
|
300
|
+
linhas[inicio] = linhas[inicio].replace('{}', '');
|
|
301
|
+
let fim = inicio + 1;
|
|
302
|
+
while (fim < linhas.length && !/^[^\s#][^:]*:/.test(linhas[fim]))
|
|
303
|
+
fim++;
|
|
304
|
+
for (const [k, v] of Object.entries((0, experiencia_1.validarPreferencias)(conteudo.owner))) {
|
|
305
|
+
const re = new RegExp('^ (?:' + k + '|"' + k + '"|\x27' + k + '\x27)\\s*:');
|
|
306
|
+
const indices = linhas.slice(inicio + 1, fim).map((l, i) => re.test(l) ? i + inicio + 1 : -1).filter(i => i >= 0);
|
|
307
|
+
if (indices.length > 1)
|
|
308
|
+
throw Error('experiencia.manifesto.conflict: preferência duplicada');
|
|
309
|
+
const linha = ` ${k}: ${JSON.stringify(v)}`;
|
|
310
|
+
// Comentário na mesma linha fica; a conferência semântica abaixo pega leitura errada.
|
|
311
|
+
if (indices.length)
|
|
312
|
+
linhas[indices[0]] = linha + (COMENTARIO_NA_LINHA.exec(linhas[indices[0]])?.[1] ?? '');
|
|
313
|
+
else {
|
|
314
|
+
linhas.splice(inicio + 1, 0, linha);
|
|
315
|
+
fim++;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const proximo = linhas.join(eol);
|
|
319
|
+
if (Buffer.byteLength(proximo) > manifest_1.LIMITE_MANIFESTO_BYTES)
|
|
320
|
+
throw Error('experiencia.manifesto.large');
|
|
321
|
+
// Sintaxes/indentações não representadas pelo editor não podem descartar dados do YAML.
|
|
322
|
+
const dados = (0, yaml_1.lerYaml)(anterior), novos = (0, yaml_1.lerYaml)(proximo);
|
|
323
|
+
if (!objeto(dados) || !objeto(novos))
|
|
324
|
+
throw Error('experiencia.manifesto.conflict');
|
|
325
|
+
const esperado = { ...dados, owner: { ...(objeto(dados.owner) ? dados.owner : {}), ...(0, experiencia_1.validarPreferencias)(conteudo.owner) } };
|
|
326
|
+
if (jsonCanonico(novos) !== jsonCanonico(esperado))
|
|
327
|
+
throw Error('experiencia.manifesto.conflict: edição perderia dados');
|
|
328
|
+
return { arquivo, anterior, proximo, modo: stat.mode };
|
|
329
|
+
}
|
|
266
330
|
function gravarEtapa(raiz, nome, conteudo, por = 'owner') {
|
|
267
331
|
const etapa = exigirEtapa(nome);
|
|
268
332
|
exigirAutor(por);
|
|
269
333
|
validarConteudo(etapa, conteudo);
|
|
270
334
|
return comTrava(raiz, () => {
|
|
335
|
+
const solicitadas = conteudo;
|
|
271
336
|
const { estado } = lerParaMutacao(raiz), anterior = estado.etapas[etapa];
|
|
272
|
-
|
|
337
|
+
// Configurar preferências não apaga objetivos ou outras respostas da etapa maestro.
|
|
338
|
+
if (etapa === 'maestro' && objeto(conteudo) && objeto(conteudo.owner) && anterior && objeto(anterior.conteudo)) {
|
|
339
|
+
conteudo = { ...anterior.conteudo, ...conteudo, owner: {
|
|
340
|
+
...(objeto(anterior.conteudo.owner) ? anterior.conteudo.owner : {}), ...conteudo.owner,
|
|
341
|
+
} };
|
|
342
|
+
validarConteudo(etapa, conteudo);
|
|
343
|
+
}
|
|
344
|
+
validarConteudo(etapa, conteudo);
|
|
345
|
+
// Persistir só chaves deste pedido: uma resposta antiga não vence edição explícita do manifesto.
|
|
346
|
+
validarConteudo(etapa, solicitadas);
|
|
347
|
+
const preferencias = etapa === 'maestro' ? prepararPreferencias(raiz, solicitadas) : null;
|
|
348
|
+
const manifestoMuda = !!preferencias && preferencias.proximo !== preferencias.anterior;
|
|
349
|
+
const mesmaResposta = !!anterior && jsonCanonico(anterior.conteudo) === jsonCanonico(conteudo);
|
|
350
|
+
// Resposta igual com o manifesto editado à mão depois: o pedido explícito vale, e a entrevista
|
|
351
|
+
// guarda autoria e horário da resposta original.
|
|
352
|
+
if (mesmaResposta && !manifestoMuda)
|
|
273
353
|
return estado;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
354
|
+
if (!mesmaResposta) {
|
|
355
|
+
const timestamp = new Date().toISOString();
|
|
356
|
+
estado.etapas[etapa] = { respondidaEm: timestamp, por, conteudo };
|
|
357
|
+
estado.atualizadoEm = dataMaisRecente([estado.atualizadoEm, timestamp]);
|
|
358
|
+
}
|
|
359
|
+
const salvarEstado = () => { if (!mesmaResposta)
|
|
360
|
+
persistir(raiz, estado); };
|
|
361
|
+
if (preferencias && manifestoMuda) {
|
|
362
|
+
const temporario = preferencias.arquivo + '.' + (0, node_crypto_1.randomUUID)() + '.tmp';
|
|
363
|
+
try {
|
|
364
|
+
fs.writeFileSync(temporario, preferencias.proximo, { flag: 'wx', mode: preferencias.modo });
|
|
365
|
+
fs.renameSync(temporario, preferencias.arquivo);
|
|
366
|
+
try {
|
|
367
|
+
salvarEstado();
|
|
368
|
+
}
|
|
369
|
+
catch (e) {
|
|
370
|
+
fs.writeFileSync(preferencias.arquivo, preferencias.anterior);
|
|
371
|
+
throw e;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
if (fs.existsSync(temporario))
|
|
376
|
+
fs.unlinkSync(temporario);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else
|
|
380
|
+
salvarEstado();
|
|
278
381
|
(0, ledger_1.registrar)((0, manifest_1.dirEstado)((0, project_state_1.projectState)(raiz).root), 'projeto', ledger_1.TIPOS_DE_EVENTO.onboardingRegistrado, { acao: 'set', etapa, por, sha256: sha(conteudo) });
|
|
279
382
|
return estado;
|
|
280
383
|
});
|
|
@@ -296,8 +399,11 @@ function resetarOnboarding(raiz, nome, por = 'owner') {
|
|
|
296
399
|
return estado;
|
|
297
400
|
});
|
|
298
401
|
}
|
|
299
|
-
function textoDaPauta(estado = onboardingPadrao()) {
|
|
402
|
+
function textoDaPauta(estado = onboardingPadrao(), owner = {}) {
|
|
403
|
+
const p = (0, experiencia_1.resolverExperiencia)(owner);
|
|
300
404
|
return ['Onboarding do projeto (' + exports.CONTRATO_ONBOARDING + ')',
|
|
405
|
+
`Experiência recomendada: ${p.language}, ${p.timezone}, resposta ${p.depth}. Ativar com esses valores, configurar ou desativar?`,
|
|
406
|
+
'Registre a escolha em maestro com {"owner":{"language":"' + p.language + '","timezone":"' + p.timezone + '","depth":"' + p.depth + '","experience":true}}; use experience:false para desativar. Consulta não registra resposta.',
|
|
301
407
|
...exports.PAUTA_ONBOARDING.map(({ etapa, pergunta }) => `${estado.etapas[etapa] ? '[respondida]' : '[pendente]'} ${etapa}: ${pergunta}`),
|
|
302
408
|
'Responda com ork onboarding set <etapa> --conteudo <JSON> --por <quem>.'].join('\n');
|
|
303
409
|
}
|
package/dist/orkmind.js
CHANGED
|
@@ -54,17 +54,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
54
54
|
};
|
|
55
55
|
})();
|
|
56
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
-
exports.DriverEmMemoria = exports.DriverCliOrkMind = exports.LIMITE_CONSULTA_MAXIMO = exports.LIMITE_CONSULTA_PADRAO = exports.ESQUEMA_ORKMIND = exports.DIMENSOES = exports.COLECOES_DE_GOVERNANCA = exports.COLECOES_DO_ORK = void 0;
|
|
57
|
+
exports.DriverEmMemoria = exports.DriverCliOrkMind = exports.LIMITE_CONSULTA_MAXIMO = exports.LIMITE_CONSULTA_PADRAO = exports.CODIGOS_DE_EMBEDDING = exports.MODELO_DE_EMBEDDING = exports.EMBED_MAX_CARACTERES = exports.EMBED_MAX_TEXTOS = exports.ESQUEMA_ORKMIND = exports.DIMENSOES = exports.COLECOES_DE_GOVERNANCA = exports.COLECOES_DO_ORK = void 0;
|
|
58
58
|
exports.enderecoDeMemoria = enderecoDeMemoria;
|
|
59
59
|
exports.lerEndereco = lerEndereco;
|
|
60
60
|
exports.violacoesDeGovernanca = violacoesDeGovernanca;
|
|
61
61
|
exports.casaTags = casaTags;
|
|
62
|
+
exports.textoDeBuscaValido = textoDeBuscaValido;
|
|
63
|
+
exports.chaveDeEmbeddingAceita = chaveDeEmbeddingAceita;
|
|
64
|
+
exports.validarPedidoDeEmbedding = validarPedidoDeEmbedding;
|
|
65
|
+
exports.conferirRespostaDeEmbedding = conferirRespostaDeEmbedding;
|
|
62
66
|
exports.criarEscopoDeLeitura = criarEscopoDeLeitura;
|
|
63
67
|
exports.validarConsultaDelimitada = validarConsultaDelimitada;
|
|
64
68
|
exports.tagsDoEscopo = tagsDoEscopo;
|
|
65
69
|
exports.pertenceAoEscopo = pertenceAoEscopo;
|
|
66
70
|
exports.concluirConsultaDelimitada = concluirConsultaDelimitada;
|
|
67
71
|
exports.entradaDoJson = entradaDoJson;
|
|
72
|
+
exports.vetorDeDuble = vetorDeDuble;
|
|
68
73
|
exports.camposNativos = camposNativos;
|
|
69
74
|
exports.agenteProspectivoValido = agenteProspectivoValido;
|
|
70
75
|
exports.prospectivaCompativel = prospectivaCompativel;
|
|
@@ -180,6 +185,66 @@ function casaTags(entrada, consulta) {
|
|
|
180
185
|
}
|
|
181
186
|
return todasCasaram || (entrada.mandatory && algumaCasou);
|
|
182
187
|
}
|
|
188
|
+
/** Texto de busca: nao vazio, sem caractere de controle, ate 2.000 caracteres. */
|
|
189
|
+
function textoDeBuscaValido(texto) {
|
|
190
|
+
return typeof texto === 'string' && texto.trim() !== '' && texto.length <= 2000 && !/[\x00-\x1f\x7f]/.test(texto);
|
|
191
|
+
}
|
|
192
|
+
/** Limites da operacao `embed` (D10): lote e tamanho de texto, sem truncar em silencio. */
|
|
193
|
+
exports.EMBED_MAX_TEXTOS = 32;
|
|
194
|
+
exports.EMBED_MAX_CARACTERES = 24_000;
|
|
195
|
+
/** Domicilio unico do formato `org/nome` de modelo de embedding (primario e fallback). */
|
|
196
|
+
exports.MODELO_DE_EMBEDDING = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
197
|
+
/**
|
|
198
|
+
* O valor lido da variavel declarada so vale como chave se nao parecer outra coisa: URL, DSN,
|
|
199
|
+
* texto com espaco ou a propria DSN da memoria nunca vao ao provider (nome trocado por engano).
|
|
200
|
+
*/
|
|
201
|
+
function chaveDeEmbeddingAceita(valor, dsn) {
|
|
202
|
+
if (valor === '' || /\s/.test(valor) || valor.includes('://') || valor === dsn.trim())
|
|
203
|
+
return false;
|
|
204
|
+
// A senha da DSN sozinha tambem nunca vai ao provider como Bearer.
|
|
205
|
+
try {
|
|
206
|
+
const senha = new URL(dsn).password;
|
|
207
|
+
if (senha && (valor === senha || valor === decodeURIComponent(senha)))
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
catch { /* DSN opaca (key=value): a comparacao inteira acima ja vale */ }
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
/** Codigos tipados que a ponte pode devolver na operacao `embed`. */
|
|
214
|
+
exports.CODIGOS_DE_EMBEDDING = [
|
|
215
|
+
'memory.embed.invalid', 'embeddings.chave-ausente', 'embeddings.provider-indisponivel', 'embeddings.timeout',
|
|
216
|
+
'embeddings.local-ausente', 'embeddings.dependencia-ausente', 'embeddings.dimensao-divergente',
|
|
217
|
+
'embeddings.conteudo-recusado',
|
|
218
|
+
];
|
|
219
|
+
/** Copia validada: o que vai a ponte e exatamente o que foi conferido aqui. */
|
|
220
|
+
function validarPedidoDeEmbedding(bruto) {
|
|
221
|
+
const p = bruto;
|
|
222
|
+
if (!objetoDeConsulta(p) || Object.keys(p).some(k => !['papel', 'alvo', 'modelo', 'dim', 'textos'].includes(k)) ||
|
|
223
|
+
!['consulta', 'documento'].includes(p.papel) || !['primario', 'fallback'].includes(p.alvo) ||
|
|
224
|
+
typeof p.modelo !== 'string' || !exports.MODELO_DE_EMBEDDING.test(p.modelo) ||
|
|
225
|
+
!Number.isInteger(p.dim) || Number(p.dim) < 32 || Number(p.dim) > 4096 ||
|
|
226
|
+
!Array.isArray(p.textos) || p.textos.length < 1 || p.textos.length > exports.EMBED_MAX_TEXTOS ||
|
|
227
|
+
!p.textos.every(t => typeof t === 'string' && t.trim() !== '' && t.length <= exports.EMBED_MAX_CARACTERES)) {
|
|
228
|
+
throw new Error('memory.embed.invalid');
|
|
229
|
+
}
|
|
230
|
+
return { papel: p.papel, alvo: p.alvo,
|
|
231
|
+
modelo: p.modelo, dim: Number(p.dim), textos: [...p.textos] };
|
|
232
|
+
}
|
|
233
|
+
/** Resposta da ponte conferida: um vetor finito por texto, todos na dimensao pedida. */
|
|
234
|
+
function conferirRespostaDeEmbedding(r, pedido) {
|
|
235
|
+
const o = r;
|
|
236
|
+
if (!objetoDeConsulta(o) || o.alvo !== pedido.alvo || o.modelo !== pedido.modelo || o.dim !== pedido.dim ||
|
|
237
|
+
!Array.isArray(o.vetores))
|
|
238
|
+
throw new Error('memory.transport.embed');
|
|
239
|
+
if (o.vetores.length !== pedido.textos.length || !o.vetores.every(v => Array.isArray(v) && v.length === pedido.dim &&
|
|
240
|
+
v.every(x => typeof x === 'number' && Number.isFinite(x))))
|
|
241
|
+
throw new Error('embeddings.dimensao-divergente');
|
|
242
|
+
const truncados = o.truncados === undefined ? [] : o.truncados;
|
|
243
|
+
if (!Array.isArray(truncados) || !truncados.every(i => Number.isInteger(i) && i >= 0 && i < pedido.textos.length)) {
|
|
244
|
+
throw new Error('memory.transport.embed');
|
|
245
|
+
}
|
|
246
|
+
return { alvo: pedido.alvo, modelo: pedido.modelo, dim: pedido.dim, vetores: o.vetores, truncados: truncados };
|
|
247
|
+
}
|
|
183
248
|
exports.LIMITE_CONSULTA_PADRAO = 100;
|
|
184
249
|
exports.LIMITE_CONSULTA_MAXIMO = 1000;
|
|
185
250
|
function objetoDeConsulta(x) {
|
|
@@ -314,9 +379,19 @@ class DriverCliOrkMind {
|
|
|
314
379
|
throw new Error('memory.transport.interpreter: shebang nao suportado');
|
|
315
380
|
return match[2] ? [match[1], match[2]] : [match[1]];
|
|
316
381
|
}
|
|
382
|
+
/** Valor da chave de embedding no ambiente do `ork`; nunca guardado no driver. */
|
|
383
|
+
chaveDeEmbedding() {
|
|
384
|
+
const nome = this.config.variavelDaChaveDeEmbedding ?? '';
|
|
385
|
+
const valor = nome ? (process.env[nome] ?? '').trim() : '';
|
|
386
|
+
return chaveDeEmbeddingAceita(valor, this.config.dsn) ? valor : '';
|
|
387
|
+
}
|
|
317
388
|
contemSegredo(valor) {
|
|
318
389
|
const segredo = this.config.dsn;
|
|
319
390
|
const proibidos = [segredo];
|
|
391
|
+
const nome = this.config.variavelDaChaveDeEmbedding ?? '';
|
|
392
|
+
const chave = nome ? (process.env[nome] ?? '').trim() : '';
|
|
393
|
+
if (chave.length >= 12)
|
|
394
|
+
proibidos.push(chave);
|
|
320
395
|
try {
|
|
321
396
|
const senha = new URL(segredo).password;
|
|
322
397
|
if (senha.length >= 12)
|
|
@@ -328,7 +403,7 @@ class DriverCliOrkMind {
|
|
|
328
403
|
: !!x && typeof x === 'object' && Object.entries(x).some(([k, v]) => examinar(k) || examinar(v));
|
|
329
404
|
return examinar(valor);
|
|
330
405
|
}
|
|
331
|
-
rodar(pedido) {
|
|
406
|
+
rodar(pedido, timeoutMs = this.config.timeoutMs) {
|
|
332
407
|
if (!this.config.dsn.trim())
|
|
333
408
|
throw new Error('dsn.env-ausente');
|
|
334
409
|
if (this.contemSegredo(pedido))
|
|
@@ -338,13 +413,19 @@ class DriverCliOrkMind {
|
|
|
338
413
|
path.join(__dirname, '../../assets/orkmind_bridge.py')].find(p => fs.existsSync(p));
|
|
339
414
|
if (!ponte)
|
|
340
415
|
throw new Error('memory.transport.bridge: ponte ausente no pacote');
|
|
341
|
-
|
|
342
|
-
|
|
416
|
+
// I-38 (D1): `embed` nao toca a base, entao nao recebe a DSN; a chave vai SO ao `embed` primario.
|
|
417
|
+
const embed = pedido.op === 'embed';
|
|
418
|
+
const env = embed ? { PYTHONDONTWRITEBYTECODE: '1' }
|
|
419
|
+
: { ORKMIND_DATABASE_URL: this.config.dsn, PYTHONDONTWRITEBYTECODE: '1' };
|
|
420
|
+
for (const nome of ['PATH', 'HOME', 'LANG', 'LC_ALL', 'SYSTEMROOT', 'HF_HOME', 'HF_HUB_CACHE']) {
|
|
343
421
|
if (process.env[nome])
|
|
344
422
|
env[nome] = process.env[nome];
|
|
345
423
|
}
|
|
424
|
+
const chave = embed && pedido.alvo === 'primario' ? this.chaveDeEmbedding() : '';
|
|
425
|
+
if (chave)
|
|
426
|
+
env.ORKMIND_EMBEDDING_API_KEY = chave;
|
|
346
427
|
const r = (0, node_child_process_1.spawnSync)(python, [...prefixo, ponte], {
|
|
347
|
-
encoding: 'utf8', input: JSON.stringify(pedido), timeout:
|
|
428
|
+
encoding: 'utf8', input: JSON.stringify(pedido), timeout: timeoutMs,
|
|
348
429
|
maxBuffer: 32 * 1024 * 1024, env,
|
|
349
430
|
});
|
|
350
431
|
// Nunca reutilizar stdout, stderr, error.message ou causa de uma falha do filho.
|
|
@@ -359,7 +440,7 @@ class DriverCliOrkMind {
|
|
|
359
440
|
code = JSON.parse(r.stdout)?.error;
|
|
360
441
|
}
|
|
361
442
|
catch { /* nenhum detalhe do filho */ }
|
|
362
|
-
throw new Error(['memory.schema.absent', 'memory.legacy.provenance-collision', 'memory.prospective.marker-invalid', 'memory.native.schema-mismatch', 'memory.query.invalid', 'memory.query.window-saturated', 'memory.query.scope-violation'].includes(code) ? code : 'memory.transport.failed');
|
|
443
|
+
throw new Error(['memory.schema.absent', 'memory.legacy.provenance-collision', 'memory.prospective.marker-invalid', 'memory.native.schema-mismatch', 'memory.query.invalid', 'memory.query.window-saturated', 'memory.query.scope-violation', 'memory.health.invalid', 'memory.fts.invalid', ...exports.CODIGOS_DE_EMBEDDING].includes(code) ? code : 'memory.transport.failed');
|
|
363
444
|
}
|
|
364
445
|
let json;
|
|
365
446
|
try {
|
|
@@ -372,18 +453,33 @@ class DriverCliOrkMind {
|
|
|
372
453
|
throw new Error('memory.transport.secret: resposta recusada');
|
|
373
454
|
return json;
|
|
374
455
|
}
|
|
456
|
+
/** O detalhe sai do que a operacao `health` observou, nunca de frase fixa (I-38 T4). */
|
|
375
457
|
disponivel() {
|
|
376
458
|
if (this.saude)
|
|
377
459
|
return this.saude;
|
|
378
460
|
try {
|
|
379
|
-
this.
|
|
380
|
-
|
|
461
|
+
const saude = this.sondarSaude();
|
|
462
|
+
const total = Object.values(saude.contagens).reduce((a, b) => a + b, 0);
|
|
463
|
+
this.saude = { ok: true, saude, detalhe: `ponte OrkMind respondeu a sonda health (orkmind ${saude.orkmind ?? 'sem versao declarada'}, ` +
|
|
464
|
+
`${total} entrada(s) em ${Object.keys(saude.contagens).length} colecao(oes))` };
|
|
381
465
|
}
|
|
382
466
|
catch (e) {
|
|
383
467
|
this.saude = { ok: false, detalhe: e.message };
|
|
384
468
|
}
|
|
385
469
|
return this.saude;
|
|
386
470
|
}
|
|
471
|
+
sondarSaude() {
|
|
472
|
+
const r = this.rodar({ op: 'health' });
|
|
473
|
+
const contagens = r?.contagens;
|
|
474
|
+
const fallback = r?.fallback;
|
|
475
|
+
if (!objetoDeConsulta(r) || !objetoDeConsulta(contagens) || !objetoDeConsulta(fallback) ||
|
|
476
|
+
!Object.values(contagens).every(v => Number.isInteger(v) && Number(v) >= 0) ||
|
|
477
|
+
typeof fallback.dependencias !== 'boolean' || !(r.orkmind === null || typeof r.orkmind === 'string')) {
|
|
478
|
+
throw new Error('memory.transport.health');
|
|
479
|
+
}
|
|
480
|
+
return { contagens: contagens, orkmind: r.orkmind,
|
|
481
|
+
fallback: { dependencias: fallback.dependencias } };
|
|
482
|
+
}
|
|
387
483
|
contagens() {
|
|
388
484
|
const r = this.rodar({ op: 'stats' });
|
|
389
485
|
if (!r || Array.isArray(r) || typeof r !== 'object' ||
|
|
@@ -444,6 +540,21 @@ class DriverCliOrkMind {
|
|
|
444
540
|
throw new Error('memory.transport.readback');
|
|
445
541
|
return e;
|
|
446
542
|
}
|
|
543
|
+
embeddar(pedido, opcoes = {}) {
|
|
544
|
+
const p = validarPedidoDeEmbedding(pedido);
|
|
545
|
+
if (p.alvo === 'primario' && !this.chaveDeEmbedding())
|
|
546
|
+
throw new Error('embeddings.chave-ausente');
|
|
547
|
+
return conferirRespostaDeEmbedding(this.rodar({ op: 'embed', ...p }, opcoes.timeoutMs), p);
|
|
548
|
+
}
|
|
549
|
+
buscarTexto(tenant, texto) {
|
|
550
|
+
if (!textoDeConsulta(tenant, 128) || !textoDeBuscaValido(texto))
|
|
551
|
+
throw new Error('memory.fts.invalid');
|
|
552
|
+
const r = this.rodar({ op: 'fts', tenant, texto });
|
|
553
|
+
if (!objetoDeConsulta(r) || !Array.isArray(r.ids) || !r.ids.every(id => typeof id === 'string' && id)) {
|
|
554
|
+
throw new Error('memory.transport.fts');
|
|
555
|
+
}
|
|
556
|
+
return r.ids;
|
|
557
|
+
}
|
|
447
558
|
submeterHandoff(pedido) {
|
|
448
559
|
try {
|
|
449
560
|
const r = this.rodar({ op: 'handoff', pedido });
|
|
@@ -458,6 +569,15 @@ class DriverCliOrkMind {
|
|
|
458
569
|
}
|
|
459
570
|
}
|
|
460
571
|
exports.DriverCliOrkMind = DriverCliOrkMind;
|
|
572
|
+
/** Vetor do dublê: cada palavra soma 1 numa posicao por hash; normalizado. Deterministico. */
|
|
573
|
+
function vetorDeDuble(texto, dim) {
|
|
574
|
+
const v = new Array(dim).fill(0);
|
|
575
|
+
const palavras = texto.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
576
|
+
for (const palavra of palavras)
|
|
577
|
+
v[(0, node_crypto_1.createHash)('sha256').update(palavra).digest().readUInt32BE(0) % dim] += 1;
|
|
578
|
+
const norma = Math.hypot(...v);
|
|
579
|
+
return norma === 0 ? v.map((_, i) => (i === 0 ? 1 : 0)) : v.map(x => x / norma);
|
|
580
|
+
}
|
|
461
581
|
function tagsFixasDaDecisao(tags) {
|
|
462
582
|
const fases = new Set(['GOAL', 'PLAN', 'GO', 'CHECK', 'SHIP', 'MASTER']);
|
|
463
583
|
return Object.fromEntries(Object.entries(tags).filter(([k]) => k !== 'agent').map(([k, values]) => [k, [...new Set(values.filter(v => !(k === 'skill' && fases.has(v)) &&
|
|
@@ -547,6 +667,10 @@ class DriverEmMemoria {
|
|
|
547
667
|
contador = 0;
|
|
548
668
|
/** Quando false, o driver se declara indisponivel: e o teste de degradacao. */
|
|
549
669
|
ligado = true;
|
|
670
|
+
/** I-38: quais alvos de embedding o dublê atende; o resto responde com o motivo tipado. */
|
|
671
|
+
embedding = { primario: true, fallback: true };
|
|
672
|
+
/** Pedidos de embedding recebidos, para as assercoes de custo (chamadas ao provider). */
|
|
673
|
+
pedidosDeEmbedding = [];
|
|
550
674
|
constructor(entradas = []) {
|
|
551
675
|
for (const e of entradas)
|
|
552
676
|
this.entradas.push(e);
|
|
@@ -554,7 +678,8 @@ class DriverEmMemoria {
|
|
|
554
678
|
}
|
|
555
679
|
disponivel() {
|
|
556
680
|
return this.ligado
|
|
557
|
-
? { ok: true, detalhe: `driver em memoria com ${this.entradas.length} entrada(s)
|
|
681
|
+
? { ok: true, detalhe: `driver em memoria com ${this.entradas.length} entrada(s)`,
|
|
682
|
+
saude: { contagens: this.contagens(), orkmind: null, fallback: { dependencias: this.embedding.fallback } } }
|
|
558
683
|
: { ok: false, detalhe: 'driver em memoria desligado' };
|
|
559
684
|
}
|
|
560
685
|
adicionar(entrada) {
|
|
@@ -664,6 +789,35 @@ class DriverEmMemoria {
|
|
|
664
789
|
return { ok: true, id: handoff.id, duplicada: false, collection: 'handoff', detalhe: 'duplo de transporte G3',
|
|
665
790
|
session_entry_id: sessao.id, package_entry_id: pacote.id, package_id: packageId, readback: true };
|
|
666
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Dublê declarado de embedding: saco de palavras com hash, sem semantica nenhuma. Serve para
|
|
794
|
+
* provar contrato (dimensao, tenant, idempotencia, custo) sem rede e sem modelo.
|
|
795
|
+
*/
|
|
796
|
+
embeddar(pedido) {
|
|
797
|
+
const p = validarPedidoDeEmbedding(pedido);
|
|
798
|
+
if (!this.ligado)
|
|
799
|
+
throw new Error('memory.transport.unavailable');
|
|
800
|
+
if (!this.embedding[p.alvo])
|
|
801
|
+
throw new Error(p.alvo === 'primario' ? 'embeddings.chave-ausente' : 'embeddings.local-ausente');
|
|
802
|
+
this.pedidosDeEmbedding.push(p);
|
|
803
|
+
return { alvo: p.alvo, modelo: p.modelo, dim: p.dim, vetores: p.textos.map(t => vetorDeDuble(t, p.dim)) };
|
|
804
|
+
}
|
|
805
|
+
/** Dublê de FTS: todas as palavras da consulta no conteudo, sem stemming, com tenant obrigatorio. */
|
|
806
|
+
buscarTexto(tenant, texto) {
|
|
807
|
+
if (!textoDeConsulta(tenant, 128) || !textoDeBuscaValido(texto))
|
|
808
|
+
throw new Error('memory.fts.invalid');
|
|
809
|
+
if (!this.ligado)
|
|
810
|
+
throw new Error('memory.transport.unavailable');
|
|
811
|
+
const palavras = (t) => t.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
812
|
+
const consulta = palavras(texto);
|
|
813
|
+
return this.entradas
|
|
814
|
+
.filter(e => exports.COLECOES_DO_ORK.includes(e.collection) && (e.tags.project ?? []).includes(tenant))
|
|
815
|
+
.map(e => ({ id: e.id, conteudo: palavras(e.content) }))
|
|
816
|
+
.filter(e => consulta.every(p => e.conteudo.includes(p)))
|
|
817
|
+
.map(e => ({ id: e.id, peso: e.conteudo.filter(p => consulta.includes(p)).length }))
|
|
818
|
+
.sort((a, b) => b.peso - a.peso || a.id.localeCompare(b.id))
|
|
819
|
+
.map(e => e.id);
|
|
820
|
+
}
|
|
667
821
|
/** So para os testes: injeta uma entrada que o `ork` nunca gravaria (humano, mandatory). */
|
|
668
822
|
semear(entrada) {
|
|
669
823
|
this.entradas.push(entrada);
|
|
@@ -685,6 +839,8 @@ function configDoManifesto(manifesto) {
|
|
|
685
839
|
dsn: variavel ? (process.env[variavel] ?? '') : '',
|
|
686
840
|
timeoutMs: manifesto.memory.timeout_ms || 15000,
|
|
687
841
|
tenant: manifesto.memory.tenant || manifesto.project.name,
|
|
842
|
+
// Lido direto do bloco: este modulo carrega sem o parser do manifesto (memory-native-schema).
|
|
843
|
+
variavelDaChaveDeEmbedding: manifesto.memory.embedding?.provider === 'openrouter' ? manifesto.memory.embedding.api_key_env : '',
|
|
688
844
|
};
|
|
689
845
|
}
|
|
690
846
|
/** Texto acionavel para cada motivo de degradacao. */
|