@orkastery/cli 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/claude-code/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/ork.md +9 -0
- package/adapters/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
- package/adapters/openclaw/README.md +24 -4
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +46 -11
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +49 -11
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +39 -5
- package/dist/adapters/codex.js +4 -3
- package/dist/agents-md.js +1 -0
- package/dist/auditrun.js +5 -1
- package/dist/board.js +16 -14
- package/dist/branch-de-estado.js +66 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/ci.js +8 -1
- package/dist/claims.js +39 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/docs.js +48 -9
- package/dist/doctor-pulse-cron.js +194 -0
- package/dist/doctor-rede.js +149 -0
- package/dist/doctor.js +89 -9
- package/dist/entrega-pr.js +20 -8
- package/dist/esgotamentos.js +181 -0
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-estado.js +14 -6
- package/dist/fabrica-publicar.js +6 -0
- package/dist/fechamento.js +20 -66
- package/dist/forja.js +31 -7
- package/dist/gates.js +5 -1
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +9 -0
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/horario.js +27 -0
- package/dist/hosts.js +52 -10
- package/dist/impedimento.js +205 -0
- package/dist/index.js +391 -74
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +3 -1
- package/dist/init.js +17 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +91 -8
- package/dist/intelligence-graph-contexto.js +215 -0
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/leases.js +664 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +3 -0
- package/dist/licoes.js +1 -0
- package/dist/maestro-runtime.js +4 -1
- package/dist/manifest.js +20 -1
- package/dist/master-nota.js +14 -5
- package/dist/master.js +129 -12
- package/dist/mcp-artifacts.js +34 -3
- package/dist/mcp-git.js +20 -3
- package/dist/mcp-grafo-worker.js +5 -3
- package/dist/mcp-grafo.js +50 -4
- package/dist/mcp-server.js +14 -1
- package/dist/memoria.js +48 -6
- package/dist/network-roadmap.js +191 -16
- package/dist/ocupacao.js +11 -3
- package/dist/orkmind.js +90 -5
- package/dist/orquestracao.js +12 -10
- package/dist/parado-no-condutor.js +188 -32
- package/dist/phase.js +68 -15
- package/dist/policies.js +52 -1
- package/dist/portfolio.js +4 -1
- package/dist/projeto-alvo.js +5 -1
- package/dist/prompts.js +36 -1
- package/dist/prova-ativacao.js +75 -6
- package/dist/pulse-delivery.js +34 -2
- package/dist/pulse.js +49 -3
- package/dist/ratelimit.js +20 -4
- package/dist/recall.js +3 -1
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +336 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +69 -2
- package/dist/roadmap-status.js +23 -3
- package/dist/runtime-profiles.js +22 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-watcher-claude.js +113 -23
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -18
- package/dist/sessoes.js +83 -2
- package/dist/ship.js +85 -19
- package/dist/thread.js +192 -13
- package/dist/worktree.js +5 -2
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/skills/README.md +1 -1
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
package/assets/orkmind_bridge.py
CHANGED
|
@@ -10,6 +10,7 @@ import math
|
|
|
10
10
|
import os
|
|
11
11
|
import re
|
|
12
12
|
import sys
|
|
13
|
+
from datetime import datetime, timezone
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class LegacyProvenanceCollision(ValueError):
|
|
@@ -370,6 +371,102 @@ async def health(store):
|
|
|
370
371
|
ORK_COLLECTIONS = ('decision', 'handoff', 'rule', 'learning', 'roadmap')
|
|
371
372
|
|
|
372
373
|
|
|
374
|
+
def no_universo(entry, tenant, agora=None):
|
|
375
|
+
"""RM-038: o predicado unico do universo da busca (indice, vetor, FTS e status).
|
|
376
|
+
|
|
377
|
+
O search_by_tags governado ja tira expiradas e injection_risk; o search_by_text nao tira
|
|
378
|
+
injection_risk. O mesmo predicado vale nos dois caminhos, com o tenant conferido como item
|
|
379
|
+
de lista: texto nunca casa por substring. `agora` e o instante de ANTES da leitura: o que a
|
|
380
|
+
biblioteca devolveu como ativo continua ativo aqui, sem corrida de expiracao.
|
|
381
|
+
"""
|
|
382
|
+
tags = getattr(entry, 'tags', None)
|
|
383
|
+
projeto = tags.get('project') if isinstance(tags, dict) else None
|
|
384
|
+
expira = getattr(entry, 'expires_at', None)
|
|
385
|
+
if expira is not None and expira.tzinfo is None:
|
|
386
|
+
expira = expira.replace(tzinfo=timezone.utc)
|
|
387
|
+
return (getattr(entry, 'collection', None) in ORK_COLLECTIONS
|
|
388
|
+
and isinstance(projeto, list) and tenant in projeto
|
|
389
|
+
and not getattr(entry, 'injection_risk', False)
|
|
390
|
+
and (expira is None or expira > (agora or datetime.now(timezone.utc))))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
async def ate_o_fim(contar, ler):
|
|
394
|
+
"""RM-038: le com a janela da contagem mais um; janela cheia conta e le de novo uma vez.
|
|
395
|
+
|
|
396
|
+
Cheia so acontece com escrita concorrente entre a contagem e a leitura. Uma escrita isolada
|
|
397
|
+
se resolve na segunda leitura; cheia de novo, a resposta e falhar alto, nunca cortar.
|
|
398
|
+
"""
|
|
399
|
+
for _ in range(2):
|
|
400
|
+
limite = await contar() + 1
|
|
401
|
+
lidas = await ler(limite)
|
|
402
|
+
if len(lidas) < limite:
|
|
403
|
+
return lidas
|
|
404
|
+
raise QueryError('memory.query.window-saturated')
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def universo_contract(request):
|
|
408
|
+
valid = isinstance(request, dict) and set(request) == {'op', 'tenant'}
|
|
409
|
+
tenant = request.get('tenant') if valid else None
|
|
410
|
+
if (not valid or request['op'] != 'universo' or not isinstance(tenant, str) or not tenant.strip()
|
|
411
|
+
or len(tenant) > 128 or re.search(r'[\x00-\x1f\x7f]', tenant) is not None):
|
|
412
|
+
raise QueryError('memory.universo.invalid')
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# Contagem SO de numeros: nenhuma coluna de conteudo sai da base por aqui. O tenant e o mesmo
|
|
416
|
+
# predicado da leitura governada por tag (`tags @> {"project": [tenant]}`), que usa o indice de tags.
|
|
417
|
+
FORA_DA_BUSCA_SQL = """
|
|
418
|
+
SELECT
|
|
419
|
+
count(*) FILTER (WHERE collection = ANY(%s) AND injection_risk
|
|
420
|
+
AND (expires_at IS NULL OR expires_at > now())) AS injecao,
|
|
421
|
+
count(*) FILTER (WHERE collection = ANY(%s) AND expires_at IS NOT NULL AND expires_at <= now()) AS expiradas,
|
|
422
|
+
count(*) FILTER (WHERE NOT (collection = ANY(%s))) AS outras_colecoes
|
|
423
|
+
FROM memories
|
|
424
|
+
WHERE tags @> jsonb_build_object('project', jsonb_build_array(%s::text))
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
async def contar_fora_da_busca(store, tenant):
|
|
429
|
+
"""RM-038: quantas entradas do tenant ficam fora do universo e por que.
|
|
430
|
+
|
|
431
|
+
A API governada nao devolve o que filtra; a contagem le a tabela pela conexao do adapter
|
|
432
|
+
pgvector (o mesmo acesso do lock de add e handoff), so com count(*). Em outro backend a
|
|
433
|
+
contagem nao e medida e volta None.
|
|
434
|
+
"""
|
|
435
|
+
if getattr(getattr(store, 'capabilities', None), 'backend', None) != 'pgvector':
|
|
436
|
+
return None
|
|
437
|
+
conn = await store.inner._get_conn()
|
|
438
|
+
colecoes = list(ORK_COLLECTIONS)
|
|
439
|
+
cur = await conn.execute(FORA_DA_BUSCA_SQL, (colecoes, colecoes, colecoes, tenant))
|
|
440
|
+
linha = await cur.fetchone()
|
|
441
|
+
return {'injecao': int(linha['injecao']), 'expiradas': int(linha['expiradas']),
|
|
442
|
+
'outrasColecoes': int(linha['outras_colecoes'])}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
async def universo(request, store):
|
|
446
|
+
"""RM-038: o universo da busca do tenant, lido ate o fim, uma leitura governada por colecao.
|
|
447
|
+
|
|
448
|
+
O tenant e filtro na origem: texto de outro tenant nunca sai da base por aqui. A janela e a
|
|
449
|
+
colecao inteira mais um (ate_o_fim). Entrada fora do predicado e violacao. O vetor gravado na
|
|
450
|
+
base nao viaja: o ork nao o usa.
|
|
451
|
+
"""
|
|
452
|
+
tenant = request['tenant']
|
|
453
|
+
agora = datetime.now(timezone.utc)
|
|
454
|
+
entradas = []
|
|
455
|
+
for colecao in ORK_COLLECTIONS:
|
|
456
|
+
async def contar(colecao=colecao):
|
|
457
|
+
return await store.count(colecao)
|
|
458
|
+
async def ler(limite, colecao=colecao):
|
|
459
|
+
return await store.search_by_tags({'project': [tenant]}, collection=colecao, limit=limite)
|
|
460
|
+
lidas = await ate_o_fim(contar, ler)
|
|
461
|
+
if any(e.collection != colecao or not no_universo(e, tenant, agora) for e in lidas):
|
|
462
|
+
raise QueryError('memory.query.scope-violation')
|
|
463
|
+
entradas.extend(lidas)
|
|
464
|
+
if len({e.id for e in entradas}) != len(entradas):
|
|
465
|
+
raise QueryError('memory.query.scope-violation')
|
|
466
|
+
return {'entradas': [e.model_dump(mode='json', exclude={'embedding'}) for e in entradas],
|
|
467
|
+
'foraDaBusca': await contar_fora_da_busca(store, tenant)}
|
|
468
|
+
|
|
469
|
+
|
|
373
470
|
def fts_contract(request):
|
|
374
471
|
valid = isinstance(request, dict) and set(request) == {'op', 'tenant', 'texto'}
|
|
375
472
|
def text(value, maximum):
|
|
@@ -379,14 +476,16 @@ def fts_contract(request):
|
|
|
379
476
|
|
|
380
477
|
|
|
381
478
|
async def fts(request, store):
|
|
382
|
-
"""FTS da biblioteca (I-38 D9)
|
|
479
|
+
"""FTS da biblioteca (I-38 D9) restrito ao universo da busca (RM-038): so ids, na ordem do ranking."""
|
|
383
480
|
# A janela cobre a base inteira: ausencia so conta quando nada ficou de fora do corte.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
481
|
+
agora = datetime.now(timezone.utc)
|
|
482
|
+
async def contar():
|
|
483
|
+
return await store.count()
|
|
484
|
+
async def ler(limite):
|
|
485
|
+
return await store.search_by_text(request['texto'], limit=limite)
|
|
486
|
+
entries = await ate_o_fim(contar, ler)
|
|
487
|
+
# O search_by_text alcanca injection_risk; o universo nao. Mesmo predicado do indice.
|
|
488
|
+
return {'ids': [e.id for e in entries if no_universo(e, request['tenant'], agora)]}
|
|
390
489
|
|
|
391
490
|
|
|
392
491
|
async def execute(request, store):
|
|
@@ -403,6 +502,9 @@ async def execute(request, store):
|
|
|
403
502
|
if op == 'fts':
|
|
404
503
|
fts_contract(request)
|
|
405
504
|
return await fts(request, store)
|
|
505
|
+
if op == 'universo':
|
|
506
|
+
universo_contract(request)
|
|
507
|
+
return await universo(request, store)
|
|
406
508
|
if op == 'query':
|
|
407
509
|
tags, collection, limit = query_contract(request)
|
|
408
510
|
# GovernedStore conserva validade, anti-injection e visibilidade existentes.
|
|
@@ -417,8 +519,12 @@ async def execute(request, store):
|
|
|
417
519
|
return [e.model_dump(mode='json') for e in entries]
|
|
418
520
|
if op == 'export':
|
|
419
521
|
collection = request['collection']
|
|
420
|
-
|
|
421
|
-
|
|
522
|
+
async def contar():
|
|
523
|
+
return await store.count(collection)
|
|
524
|
+
async def ler(limite):
|
|
525
|
+
return await store.search_by_tags({}, collection=collection, limit=limite)
|
|
526
|
+
# RM-038: le ate o fim; janela cheia de novo e falha alta, nunca corte em silencio.
|
|
527
|
+
entries = await ate_o_fim(contar, ler)
|
|
422
528
|
return [e.model_dump(mode='json') for e in entries]
|
|
423
529
|
if op == 'get':
|
|
424
530
|
entry = await store.retrieve(request['id'])
|
|
@@ -538,6 +644,8 @@ async def main(request):
|
|
|
538
644
|
health_contract(request)
|
|
539
645
|
if isinstance(request, dict) and request.get('op') == 'fts':
|
|
540
646
|
fts_contract(request)
|
|
647
|
+
if isinstance(request, dict) and request.get('op') == 'universo':
|
|
648
|
+
universo_contract(request)
|
|
541
649
|
native_schema_fields()
|
|
542
650
|
from orkmind.core.config import OrkMindConfig
|
|
543
651
|
from orkmind.store.factory import create_store
|
|
@@ -66,6 +66,7 @@ exports.parar = parar;
|
|
|
66
66
|
exports.pararAchada = pararAchada;
|
|
67
67
|
exports.acharSessaoNasContas = acharSessaoNasContas;
|
|
68
68
|
exports.ocupacaoDeContexto = ocupacaoDeContexto;
|
|
69
|
+
const horario_1 = require("../horario");
|
|
69
70
|
const util_1 = require("../util");
|
|
70
71
|
const runtime_ambiente_1 = require("../runtime-ambiente");
|
|
71
72
|
const path = __importStar(require("node:path"));
|
|
@@ -427,12 +428,25 @@ function casarDuracao(texto, agoraMs) {
|
|
|
427
428
|
}
|
|
428
429
|
const MESES = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
429
430
|
/**
|
|
430
|
-
*
|
|
431
|
+
* RM-037 (fatia 5): o fuso IANA entre parenteses logo depois da hora (`resets 4:40am (<Area>/<Cidade>)`),
|
|
432
|
+
* quando o Intl o aceita. O nome vem do texto do runtime; nenhum nome literal de fuso mora aqui (I-35).
|
|
433
|
+
*/
|
|
434
|
+
function fusoDito(texto, depois) {
|
|
435
|
+
const m = /^\s*\(([A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)*)\)/.exec(texto.slice(depois));
|
|
436
|
+
return m ? (0, horario_1.normalizarFuso)(m[1]) : undefined;
|
|
437
|
+
}
|
|
438
|
+
/** A data do dia seguinte no calendario (mes de 1 a 12), sem passar por fuso nenhum. */
|
|
439
|
+
function diaSeguinte(ano, mes, dia) {
|
|
440
|
+
const d = new Date(Date.UTC(ano, mes - 1, dia + 1));
|
|
441
|
+
return [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()];
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* N5: data e hora de relogio nas formas reais. Codex: `or try again at Sep 22nd, 2026 3:05 PM`
|
|
431
445
|
* (`%b %-d` com sufixo ordinal, `, %Y %-I:%M %p`). Claude Code, quando o reset passa de 24 h:
|
|
432
446
|
* `resets Sep 22, 3pm (<nome IANA do fuso>)` e, em outro ano, `resets Sep 22, 2027, 3:05pm`. Sem ano,
|
|
433
|
-
* vale o ano corrente, ou o seguinte quando a data ja ficou mais de um dia para tras.
|
|
434
|
-
*
|
|
435
|
-
*
|
|
447
|
+
* vale o ano corrente, ou o seguinte quando a data ja ficou mais de um dia para tras. RM-037 (fatia 5):
|
|
448
|
+
* com o fuso entre parenteses, a hora e a desse fuso (`fusoDito`); sem ele, ou com um nome que o Intl
|
|
449
|
+
* recusa, a hora e lida no relogio desta maquina, como antes. `agoraMs` e a ancora: o instante da mensagem.
|
|
436
450
|
*/
|
|
437
451
|
function casarData(texto, agoraMs) {
|
|
438
452
|
const m = texto.match(/(?:try again at|reset[a-z]*(?:\s+at)?)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(?:(\d{4}),?\s*)?(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*([ap]\.?m\.?)?(?![a-z0-9])/i);
|
|
@@ -449,6 +463,16 @@ function casarData(texto, agoraMs) {
|
|
|
449
463
|
hora = 0;
|
|
450
464
|
if (dia < 1 || dia > 31 || hora > 23 || minuto > 59)
|
|
451
465
|
return null;
|
|
466
|
+
const fuso = fusoDito(texto, (m.index ?? 0) + m[0].length);
|
|
467
|
+
if (fuso) {
|
|
468
|
+
const ano = m[3] !== undefined ? Number(m[3]) : Number((0, horario_1.partesLocais)(agoraMs, fuso).ano);
|
|
469
|
+
let alvo = (0, horario_1.instanteNoFuso)(ano, mes + 1, dia, hora, minuto, fuso);
|
|
470
|
+
if (!Number.isFinite(alvo))
|
|
471
|
+
return null;
|
|
472
|
+
if (m[3] === undefined && alvo < agoraMs - 24 * 3600 * 1000)
|
|
473
|
+
alvo = (0, horario_1.instanteNoFuso)(ano + 1, mes + 1, dia, hora, minuto, fuso);
|
|
474
|
+
return Number.isFinite(alvo) ? { resetEm: new Date(alvo).toISOString(), trecho: m[0].trim() } : null;
|
|
475
|
+
}
|
|
452
476
|
const base = new Date(agoraMs);
|
|
453
477
|
const ano = m[3] !== undefined ? Number(m[3]) : base.getFullYear();
|
|
454
478
|
const alvo = new Date(ano, mes, dia, hora, minuto, 0, 0);
|
|
@@ -472,7 +496,17 @@ function casarRelogio(texto, agoraMs) {
|
|
|
472
496
|
hora = 0;
|
|
473
497
|
if (hora > 23 || minuto > 59)
|
|
474
498
|
return null;
|
|
475
|
-
//
|
|
499
|
+
// RM-037 (fatia 5): a proxima ocorrencia daquela hora depois da ancora, no fuso dito pelo runtime.
|
|
500
|
+
const fuso = fusoDito(texto, (m.index ?? 0) + m[0].length);
|
|
501
|
+
if (fuso) {
|
|
502
|
+
const p = (0, horario_1.partesLocais)(agoraMs, fuso);
|
|
503
|
+
let alvo = (0, horario_1.instanteNoFuso)(Number(p.ano), Number(p.mes), Number(p.dia), hora, minuto, fuso);
|
|
504
|
+
if (alvo <= agoraMs)
|
|
505
|
+
alvo = (0, horario_1.instanteNoFuso)(...diaSeguinte(Number(p.ano), Number(p.mes), Number(p.dia)), hora, minuto, fuso);
|
|
506
|
+
if (Number.isFinite(alvo))
|
|
507
|
+
return { resetEm: new Date(alvo).toISOString(), trecho: m[0].trim() };
|
|
508
|
+
}
|
|
509
|
+
// Sem fuso dito: a proxima ocorrencia daquela hora de relogio a partir da ancora, no fuso da maquina.
|
|
476
510
|
const base = new Date(agoraMs);
|
|
477
511
|
const alvo = new Date(base.getFullYear(), base.getMonth(), base.getDate(), hora, minuto, 0, 0);
|
|
478
512
|
if (alvo.getTime() <= agoraMs)
|
package/dist/adapters/codex.js
CHANGED
|
@@ -174,9 +174,10 @@ function consultarRollouts(todas = false, casa = casaDoCodex()) {
|
|
|
174
174
|
continue;
|
|
175
175
|
try {
|
|
176
176
|
const fd = fs.openSync(arquivo, 'r');
|
|
177
|
-
let primeira, cauda;
|
|
177
|
+
let primeira, cauda, atividadeEm;
|
|
178
178
|
try {
|
|
179
|
-
const
|
|
179
|
+
const st = fs.fstatSync(fd), tamanho = st.size;
|
|
180
|
+
atividadeEm = st.mtimeMs;
|
|
180
181
|
const cab = Buffer.alloc(Math.min(tamanho, 65536));
|
|
181
182
|
fs.readSync(fd, cab, 0, cab.length, 0);
|
|
182
183
|
primeira = cab.toString('utf8').split('\n')[0];
|
|
@@ -207,7 +208,7 @@ function consultarRollouts(todas = false, casa = casaDoCodex()) {
|
|
|
207
208
|
catch { /* primeira/última linha do recorte pode estar parcial */ }
|
|
208
209
|
}
|
|
209
210
|
if (todas || state !== 'completed')
|
|
210
|
-
sessoes.push({ sessionId: p.id, cwd: p.cwd, state, kind: 'codex-rollout' });
|
|
211
|
+
sessoes.push({ sessionId: p.id, cwd: p.cwd, state, kind: 'codex-rollout', atividadeEm });
|
|
211
212
|
}
|
|
212
213
|
catch {
|
|
213
214
|
falhar(arquivo, 'leitura ou metadados inválidos ou duplicados');
|
package/dist/agents-md.js
CHANGED
|
@@ -60,6 +60,7 @@ Projeto: **${seguro(manifesto.project.name)}** (${seguro(manifesto.project.abbre
|
|
|
60
60
|
- Edite produto somente na worktree vinculada; estado em \`.orkastery/\` pertence ao núcleo.
|
|
61
61
|
- PLAN não implementa. GO usa commits atômicos. CHECK não corrige. SHIP acontece por \`ork ship\`.
|
|
62
62
|
- Toda alegação exige claim e comando reproduzível; self-report não é evidência.
|
|
63
|
+
- MASTER por omissão só da sua thread: \`ork master <thread> --aceitar-omissao\`; sem a thread, fecha também as entregas de outras frentes.
|
|
63
64
|
${comandos.length ? `\nVerificações do manifesto:\n\n${comandos.join('\n')}\n` : ''}
|
|
64
65
|
Este bloco é mantido por \`ork init\`. Edite livremente o restante do arquivo.
|
|
65
66
|
${exports.FIM_AGENTS_ORK}`;
|
package/dist/auditrun.js
CHANGED
|
@@ -893,13 +893,14 @@ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
|
|
|
893
893
|
};
|
|
894
894
|
}
|
|
895
895
|
const modo = opcoes.modo ?? manifesto.conduction.default_mode;
|
|
896
|
-
const { thread, gravada } = (0, thread_1.novaThread)(carregado, {
|
|
896
|
+
const { thread, gravada, worktreePor, worktreeFalharia } = (0, thread_1.novaThread)(carregado, {
|
|
897
897
|
nome: opcoes.nome ?? achado.titulo,
|
|
898
898
|
modo,
|
|
899
899
|
slug: opcoes.slug,
|
|
900
900
|
assunto: opcoes.assunto,
|
|
901
901
|
worktree: opcoes.worktree ?? null,
|
|
902
902
|
criarWorktree: opcoes.criarWorktree,
|
|
903
|
+
origemDaWorktree: opcoes.origemDaWorktree,
|
|
903
904
|
dryRun: opcoes.dryRun,
|
|
904
905
|
variante: opcoes.variante ?? null,
|
|
905
906
|
});
|
|
@@ -909,6 +910,8 @@ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
|
|
|
909
910
|
achado,
|
|
910
911
|
thread,
|
|
911
912
|
pedido,
|
|
913
|
+
worktreePor,
|
|
914
|
+
worktreeFalharia,
|
|
912
915
|
motivo: null,
|
|
913
916
|
detalhe: 'simulacao (--dry-run): nenhuma thread e nenhum carimbo no board foram gravados',
|
|
914
917
|
correcao: '',
|
|
@@ -968,6 +971,7 @@ function abrirThreadDoAchado(carregado, achadoId, opcoes = {}) {
|
|
|
968
971
|
achado,
|
|
969
972
|
thread,
|
|
970
973
|
pedido,
|
|
974
|
+
worktreePor,
|
|
971
975
|
motivo: achado.claim.estado === 'nao-verificavel' ? 'claims.unverifiable' : null,
|
|
972
976
|
detalhe: achado.claim.estado === 'nao-verificavel'
|
|
973
977
|
? `AVISO: a claim ${achado.claim.id} do achado nao tem comando de verificacao (motivo tipado claims.unverifiable, que avisa e nao bloqueia)`
|
package/dist/board.js
CHANGED
|
@@ -60,7 +60,7 @@ exports.textoDoBoard = textoDoBoard;
|
|
|
60
60
|
const fs = __importStar(require("node:fs"));
|
|
61
61
|
const path = __importStar(require("node:path"));
|
|
62
62
|
const adapter = __importStar(require("./adapters/claude-bg"));
|
|
63
|
-
const
|
|
63
|
+
const sessoes_contas_1 = require("./sessoes-contas");
|
|
64
64
|
const leases_1 = require("./leases");
|
|
65
65
|
const ledger_1 = require("./ledger");
|
|
66
66
|
const manifest_1 = require("./manifest");
|
|
@@ -102,28 +102,30 @@ function threadsDeTodosOsPerfis(carregado, todos = true) {
|
|
|
102
102
|
const saida = [];
|
|
103
103
|
for (const perfil of perfis) {
|
|
104
104
|
const fila = (0, leases_1.lerFila)(perfil.raiz);
|
|
105
|
+
// RM-036 (D7): uma listagem por perfil. Por thread, a leitura do legado das worktrees se repetiria.
|
|
106
|
+
// I-36: a conducao (`exec:`) tem secao propria; o escalonador segue contando so os leases de escrita.
|
|
107
|
+
const ativos = (0, leases_1.listarLeases)(perfil.raiz).filter((l) => !(0, leases_1.expirado)(l) && (0, leases_1.tipoDoLease)(l.nome) !== 'exec');
|
|
105
108
|
for (const id of (0, thread_1.listarIds)(perfil.raiz)) {
|
|
106
109
|
const thread = (0, thread_1.lerThread)(perfil.raiz, id);
|
|
107
110
|
saida.push({
|
|
108
111
|
perfil: perfil.nome,
|
|
109
112
|
raiz: perfil.raiz,
|
|
110
113
|
thread,
|
|
111
|
-
|
|
112
|
-
leases: (0, leases_1.leasesDaThread)(perfil.raiz, id).filter((l) => (0, leases_1.tipoDoLease)(l.nome) !== 'exec').map((l) => l.nome),
|
|
114
|
+
leases: [...new Set(ativos.filter((l) => l.thread === id).map((l) => l.nome))],
|
|
113
115
|
naFila: fila.filter((p) => p.thread === id),
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
return saida;
|
|
118
120
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Estados de sessao do runtime, ou null quando ele nao pode ser consultado. RM-056 (D7): de todas
|
|
123
|
+
* as contas (a do processo e cada perfil do store), com o fantasma fora da vaga e o codex vivo.
|
|
124
|
+
*/
|
|
125
|
+
function estadosDeSessao(raiz = null, staleMin) {
|
|
121
126
|
if (!adapter.disponivel())
|
|
122
127
|
return null;
|
|
123
|
-
return
|
|
124
|
-
.listarSessoes()
|
|
125
|
-
.filter((s) => !!s.sessionId)
|
|
126
|
-
.map((s) => [s.sessionId, (0, hitl_1.estadoBruto)(s)]));
|
|
128
|
+
return (0, sessoes_contas_1.estadosNasContas)(raiz, { staleMin });
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
131
|
* RM-037 (rm037defeito, defeito 3): o portao de vaga do `ork phase run`. Com `max_parallel_threads: 5`,
|
|
@@ -210,7 +212,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
210
212
|
const max = Math.max(1, manifesto.concurrency.max_parallel_threads);
|
|
211
213
|
const staleMin = manifesto.concurrency.stale_after_min;
|
|
212
214
|
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
213
|
-
const estados = opcoes.estados === undefined ? estadosDeSessao() : opcoes.estados;
|
|
215
|
+
const estados = opcoes.estados === undefined ? estadosDeSessao(raiz, staleMin) : opcoes.estados;
|
|
214
216
|
const fila = (0, leases_1.lerFila)(raiz);
|
|
215
217
|
// I-36: a conducao (`exec:`) nao entra na conta de vagas: a ocupacao da sessao ja sai do ledger
|
|
216
218
|
// e do runtime (`avaliarOcupacao`), e conta-la de novo mudaria o escalonador.
|
|
@@ -231,7 +233,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
231
233
|
if (t.status === 'fechada' || (0, sessoes_adopt_1.ehRegistroDeAdocao)(t))
|
|
232
234
|
continue;
|
|
233
235
|
const oc = ocupacoes.get(t.id);
|
|
234
|
-
const meus = ativos.filter((l) => l.thread === t.id).map((l) => l.nome);
|
|
236
|
+
const meus = [...new Set(ativos.filter((l) => l.thread === t.id).map((l) => l.nome))];
|
|
235
237
|
const ocupa = oc.ocupaVaga || (meus.length > 0 && oc.classe === 'ociosa');
|
|
236
238
|
if (!ocupa)
|
|
237
239
|
continue;
|
|
@@ -254,7 +256,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
254
256
|
for (const t of threads) {
|
|
255
257
|
if (emAndamento.has(t.id))
|
|
256
258
|
continue;
|
|
257
|
-
const meus = ativos.filter((l) => l.thread === t.id).map((l) => l.nome);
|
|
259
|
+
const meus = [...new Set(ativos.filter((l) => l.thread === t.id).map((l) => l.nome))];
|
|
258
260
|
if (t.status === 'fechada') {
|
|
259
261
|
vagas.push({
|
|
260
262
|
thread: t.id,
|
|
@@ -286,7 +288,7 @@ function planejar(carregado, opcoes = {}) {
|
|
|
286
288
|
situacao: 'pausada',
|
|
287
289
|
motivo: 'human.pending',
|
|
288
290
|
detalhe: oc.detalhe,
|
|
289
|
-
correcao: `ork gate request ${t.id}`,
|
|
291
|
+
correcao: oc.correcao ?? `ork gate request ${t.id}`,
|
|
290
292
|
leases: meus,
|
|
291
293
|
});
|
|
292
294
|
continue;
|
|
@@ -385,7 +387,7 @@ function devolverVagas(carregado, opcoes = {}) {
|
|
|
385
387
|
const { raiz, manifesto } = carregado;
|
|
386
388
|
const staleMin = manifesto.concurrency.stale_after_min;
|
|
387
389
|
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
388
|
-
const estados = opcoes.estados === undefined ? estadosDeSessao() : opcoes.estados;
|
|
390
|
+
const estados = opcoes.estados === undefined ? estadosDeSessao(raiz, staleMin) : opcoes.estados;
|
|
389
391
|
const quem = opcoes.por ?? 'escalonador (ork board reap)';
|
|
390
392
|
const devolvidas = [];
|
|
391
393
|
for (const id of (0, thread_1.listarIds)(raiz)) {
|
package/dist/branch-de-estado.js
CHANGED
|
@@ -33,9 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.refRemota = void 0;
|
|
36
|
+
exports.refRemota = exports.remotoValido = exports.REMOTO_DO_GIT = void 0;
|
|
37
37
|
exports.git = git;
|
|
38
38
|
exports.exigirGit = exigirGit;
|
|
39
|
+
exports.remotoRedigido = remotoRedigido;
|
|
40
|
+
exports.exigirRemoto = exigirRemoto;
|
|
39
41
|
exports.pontaLocal = pontaLocal;
|
|
40
42
|
exports.buscarBranch = buscarBranch;
|
|
41
43
|
exports.jsonsDaPonta = jsonsDaPonta;
|
|
@@ -57,6 +59,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
57
59
|
const fs = __importStar(require("node:fs"));
|
|
58
60
|
const os = __importStar(require("node:os"));
|
|
59
61
|
const path = __importStar(require("node:path"));
|
|
62
|
+
const redacao_url_1 = require("./redacao-url");
|
|
60
63
|
function git(raiz, args, extra = {}) {
|
|
61
64
|
const r = (0, node_child_process_1.spawnSync)('git', args, { cwd: raiz, encoding: 'utf8', input: extra.input, timeout: extra.timeoutMs ?? 60000,
|
|
62
65
|
env: { ...process.env, ...extra.env } });
|
|
@@ -68,6 +71,42 @@ function exigirGit(raiz, args, prefixo, extra = {}) {
|
|
|
68
71
|
throw new Error(`${prefixo}.git: git ${args[0]} falhou: ${r.stderr.trim().split('\n').pop() ?? ''}`);
|
|
69
72
|
return r.stdout;
|
|
70
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Nome de remoto do git, como `origin`, `upstream` ou `meu-remoto.2`. O valor vem do manifesto
|
|
76
|
+
* versionado (`fabrica.remoto`) ou da linha de comando (`--remoto`): quem clona um repositorio roda
|
|
77
|
+
* o `orkastery.yaml` de outra pessoa. Um valor que comece com `-` viraria opcao do git, e uma URL
|
|
78
|
+
* escolheria o transporte (`ext::` roda comando): so este formato chega ao git (RM-047).
|
|
79
|
+
*/
|
|
80
|
+
exports.REMOTO_DO_GIT = /^[A-Za-z0-9][A-Za-z0-9._-]*(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/;
|
|
81
|
+
/**
|
|
82
|
+
* `true` quando o valor e nome de remoto que pode ir ao git como argumento. Suspeitas da revisao de
|
|
83
|
+
* 03/10: o git aceita `/` no nome (`time/origem`), e a 0.5.2 tambem; cada parte comeca por letra ou
|
|
84
|
+
* digito, entao a barra nao abre caminho absoluto, `.`/`..`, opcao nem transporte.
|
|
85
|
+
*/
|
|
86
|
+
const remotoValido = (remoto) => typeof remoto === 'string' && remoto.length <= 64 && exports.REMOTO_DO_GIT.test(remoto) && !remoto.includes('..') && !remoto.endsWith('.');
|
|
87
|
+
exports.remotoValido = remotoValido;
|
|
88
|
+
/**
|
|
89
|
+
* O valor recusado, para a mensagem: sem credencial de URL, sem caractere de controle e curto.
|
|
90
|
+
* A mensagem vai ao terminal, ao log da publicacao em segundo plano e ao ledger.
|
|
91
|
+
*/
|
|
92
|
+
function remotoRedigido(remoto) {
|
|
93
|
+
if (typeof remoto !== 'string')
|
|
94
|
+
return `(${remoto === null ? 'null' : typeof remoto})`;
|
|
95
|
+
const semCredencial = (0, redacao_url_1.redigirCredenciaisUrl)(remoto);
|
|
96
|
+
const curto = semCredencial.length > 40 ? `${semCredencial.slice(0, 40)}...` : semCredencial;
|
|
97
|
+
return JSON.stringify(curto);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Recusa tipada (`<prefixo>.remoto-invalido`) antes de qualquer chamada ao git com o remoto.
|
|
101
|
+
* `prefixo` e o de quem usa: `fabrica` na fabrica, `roadmap` nas reservas.
|
|
102
|
+
*/
|
|
103
|
+
function exigirRemoto(remoto, prefixo) {
|
|
104
|
+
if ((0, exports.remotoValido)(remoto))
|
|
105
|
+
return remoto;
|
|
106
|
+
throw new Error(`${prefixo}.remoto-invalido: o remoto ${remotoRedigido(remoto)} não é nome de remoto do git ` +
|
|
107
|
+
'(letras, dígitos, ".", "_", "-" e "/" entre partes, cada parte começando por letra ou dígito, sem URL); nada foi passado ao git. ' +
|
|
108
|
+
'Corrija fabrica.remoto no orkastery.yaml ou --remoto (padrão: origin).');
|
|
109
|
+
}
|
|
71
110
|
const refRemota = (remoto, branch) => `refs/remotes/${remoto}/${branch}`;
|
|
72
111
|
exports.refRemota = refRemota;
|
|
73
112
|
/** A ultima copia lida da branch nesta maquina, sem rede. `null` quando nunca foi lida. */
|
|
@@ -80,16 +119,32 @@ function pontaLocal(raiz, remoto, branch) {
|
|
|
80
119
|
* copia local com `atualizado: false`.
|
|
81
120
|
*/
|
|
82
121
|
function buscarBranch(raiz, remoto, branch, prefixo, timeoutMs, env) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
122
|
+
exigirRemoto(remoto, prefixo);
|
|
123
|
+
for (let tentativa = 1;; tentativa++) {
|
|
124
|
+
// `--`: daqui para a frente, so repositorio e refspec, nunca opcao (RM-047).
|
|
125
|
+
const r = git(raiz, ['fetch', '--quiet', '--no-tags', '--', remoto, `+refs/heads/${branch}:${(0, exports.refRemota)(remoto, branch)}`], { timeoutMs, env });
|
|
126
|
+
if (r.ok)
|
|
127
|
+
return { ponta: exigirGit(raiz, ['rev-parse', '--verify', (0, exports.refRemota)(remoto, branch)], prefixo).trim(), atualizado: true };
|
|
128
|
+
if (/couldn't find remote ref|could not find remote ref/i.test(r.stderr)) {
|
|
129
|
+
// A branch ainda nao nasceu: a primeira gravacao a cria. Copia local antiga nao vale mais.
|
|
130
|
+
git(raiz, ['update-ref', '-d', (0, exports.refRemota)(remoto, branch)]);
|
|
131
|
+
return { ponta: null, atualizado: true };
|
|
132
|
+
}
|
|
133
|
+
if (tentativa < TENTATIVAS_NA_DISPUTA_DE_REF && DISPUTA_DE_REF.test(r.stderr)) {
|
|
134
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50 * tentativa);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
return { ponta: pontaLocal(raiz, remoto, branch), atualizado: false };
|
|
90
138
|
}
|
|
91
|
-
return { ponta: pontaLocal(raiz, remoto, branch), atualizado: false };
|
|
92
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Suspeitas da revisao de 03/10: dois `fetch` simultaneos na mesma copia (o `ork network status` e a
|
|
142
|
+
* publicacao no cache da casa, a leitura e a gravacao da fabrica no clone) disputam o lock da ref
|
|
143
|
+
* remota, e o perdedor sai com "incorrect old value provided" (ou "cannot lock ref") sem que a rede
|
|
144
|
+
* tenha falhado. A ref ja foi atualizada pelo vencedor: buscar de novo da certo.
|
|
145
|
+
*/
|
|
146
|
+
const DISPUTA_DE_REF = /incorrect old value provided|cannot lock ref|unable to create '[^']*\.lock'|is at [0-9a-f]+ but expected/i;
|
|
147
|
+
const TENTATIVAS_NA_DISPUTA_DE_REF = 5;
|
|
93
148
|
/** Os `.json` de um diretorio da ponta, ja lidos. Filtro por caminho: diretorio vazio some da arvore. */
|
|
94
149
|
function jsonsDaPonta(raiz, ponta, dir, prefixo) {
|
|
95
150
|
if (!ponta)
|
|
@@ -103,6 +158,7 @@ function jsonsDaPonta(raiz, ponta, dir, prefixo) {
|
|
|
103
158
|
* indice temporario, blobs, `write-tree` e `commit-tree`. Push sem forca; recusa volta `false`.
|
|
104
159
|
*/
|
|
105
160
|
function gravarNaBranch(raiz, remoto, branch, ponta, mudancas, mensagem, prefixo, rede = {}) {
|
|
161
|
+
exigirRemoto(remoto, prefixo);
|
|
106
162
|
const indice = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ork-branch-estado-')), 'index');
|
|
107
163
|
const env = { GIT_INDEX_FILE: indice };
|
|
108
164
|
try {
|
|
@@ -122,7 +178,7 @@ function gravarNaBranch(raiz, remoto, branch, ponta, mudancas, mensagem, prefixo
|
|
|
122
178
|
const arvore = exigirGit(raiz, ['write-tree'], prefixo, { env }).trim();
|
|
123
179
|
const commit = exigirGit(raiz, ['commit-tree', arvore, ...(ponta ? ['-p', ponta] : []), '-m', mensagem], prefixo).trim();
|
|
124
180
|
// `rede`: prazo e ambiente so do que vai a rede (RM-037, achado A3: o fechamento nao espera 60 s).
|
|
125
|
-
const push = git(raiz, ['push', '--quiet', remoto, `${commit}:refs/heads/${branch}`], rede);
|
|
181
|
+
const push = git(raiz, ['push', '--quiet', '--', remoto, `${commit}:refs/heads/${branch}`], rede);
|
|
126
182
|
if (push.ok) {
|
|
127
183
|
git(raiz, ['update-ref', (0, exports.refRemota)(remoto, branch), commit]);
|
|
128
184
|
return commit;
|
package/dist/busca-semantica.js
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* deterministicos. Esta busca e superficie separada e todo resultado sai com
|
|
7
7
|
* `deterministico: false`.
|
|
8
8
|
*
|
|
9
|
-
* O universo e o
|
|
9
|
+
* O universo e o universo da busca do tenant (RM-038: `universoDaBusca`, o mesmo do indice e do
|
|
10
|
+
* status, com a governanca da biblioteca aplicada na ponte):
|
|
10
11
|
* - vetor: cosseno da consulta contra o indice local do MESMO modelo e dimensao, so para
|
|
11
12
|
* entradas com sha256 coerente; nunca compara vetores de espacos diferentes;
|
|
12
|
-
* - FTS: `search_by_text` da biblioteca,
|
|
13
|
+
* - FTS: `search_by_text` da biblioteca, restrito ao universo na ponte e intersectado aqui (id
|
|
14
|
+
* fora do universo e descartado e declarado, nunca em silencio);
|
|
13
15
|
* - fusao: RRF com k = 60.
|
|
14
16
|
* A cadeia do vetor e primario, fallback local, nenhum; sem vetor, cai para FTS com motivo tipado.
|
|
15
17
|
*/
|
|
@@ -18,6 +20,7 @@ exports.CANDIDATOS_DO_VETOR = exports.LIMITE_MAXIMO_DA_BUSCA = exports.LIMITE_PA
|
|
|
18
20
|
exports.cosseno = cosseno;
|
|
19
21
|
exports.fundirRrf = fundirRrf;
|
|
20
22
|
exports.buscarPorSignificado = buscarPorSignificado;
|
|
23
|
+
exports.textoDaBuscaPorSignificado = textoDaBuscaPorSignificado;
|
|
21
24
|
const indice_vetorial_1 = require("./indice-vetorial");
|
|
22
25
|
exports.MODOS_DE_BUSCA = ['hibrido', 'vetor', 'fts'];
|
|
23
26
|
/** D9: constante da fusao RRF. */
|
|
@@ -44,7 +47,7 @@ function fundirRrf(listas, k = exports.RRF_K) {
|
|
|
44
47
|
}
|
|
45
48
|
const resumo = (conteudo) => conteudo.split('\n')[0].slice(0, 100);
|
|
46
49
|
/** Lado vetorial: o primeiro caminho da cadeia que tem indice, responde e fala o mesmo espaco. */
|
|
47
|
-
function ladoVetorial(o) {
|
|
50
|
+
function ladoVetorial(o, entradas) {
|
|
48
51
|
const detalhes = [];
|
|
49
52
|
let motivo = null;
|
|
50
53
|
const falhar = (alvo, m, d) => {
|
|
@@ -103,7 +106,7 @@ function ladoVetorial(o) {
|
|
|
103
106
|
}
|
|
104
107
|
if (resposta.truncados?.length)
|
|
105
108
|
detalhes.push(`${alvo}: consulta acima do contexto do modelo, embedada pelo comeco`);
|
|
106
|
-
const ranking = [...(0, indice_vetorial_1.vetoresCoerentes)(lido.indice,
|
|
109
|
+
const ranking = [...(0, indice_vetorial_1.vetoresCoerentes)(lido.indice, entradas)]
|
|
107
110
|
.map(([id, v]) => ({ id, sim: cosseno(vetor, v) }))
|
|
108
111
|
.sort((a, b) => b.sim - a.sim || a.id.localeCompare(b.id));
|
|
109
112
|
return { alvo, modelo: espaco.modelo, ranking, motivo, detalhes };
|
|
@@ -112,14 +115,21 @@ function ladoVetorial(o) {
|
|
|
112
115
|
}
|
|
113
116
|
/** `ork memory search --texto`: ranking hibrido, nunca deterministico, sempre dentro do tenant. */
|
|
114
117
|
function buscarPorSignificado(o) {
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
// RM-038 (D6): entrada alheia no universo e violacao antes de embedar a consulta, nunca descarte.
|
|
119
|
+
if (o.universo.tenant !== o.tenant)
|
|
120
|
+
throw new Error('memory.query.scope-violation');
|
|
121
|
+
(0, indice_vetorial_1.conferirUniverso)(o.universo.entradas, o.tenant, o.universo.lidoEm);
|
|
122
|
+
const doUniverso = new Set(o.universo.entradas.map(e => e.id));
|
|
123
|
+
// As entradas buscadas: o universo inteiro, ou so a colecao pedida.
|
|
124
|
+
const buscadas = o.colecao ? o.universo.entradas.filter(e => e.collection === o.colecao) : o.universo.entradas;
|
|
125
|
+
const porId = new Map(buscadas.map(e => [e.id, e]));
|
|
117
126
|
const saida = { texto: o.texto, modo: o.modo, origem: 'nenhum', modeloUsado: null,
|
|
118
|
-
deterministico: false, motivo: null, detalhe: '', resultados: [], listas: { vetor: [], fts: [] }
|
|
127
|
+
deterministico: false, motivo: null, detalhe: '', resultados: [], listas: { vetor: [], fts: [] },
|
|
128
|
+
ftsForaDoUniverso: 0, coberturaDoIndice: null };
|
|
119
129
|
const detalhes = [];
|
|
120
130
|
let similaridade = new Map();
|
|
121
131
|
if (o.modo !== 'fts') {
|
|
122
|
-
const v = ladoVetorial(
|
|
132
|
+
const v = ladoVetorial(o, buscadas);
|
|
123
133
|
detalhes.push(...v.detalhes);
|
|
124
134
|
saida.motivo = v.motivo;
|
|
125
135
|
if (v.alvo) {
|
|
@@ -128,22 +138,33 @@ function buscarPorSignificado(o) {
|
|
|
128
138
|
const candidatos = v.ranking.slice(0, exports.CANDIDATOS_DO_VETOR);
|
|
129
139
|
saida.listas.vetor = candidatos.map(c => c.id);
|
|
130
140
|
similaridade = new Map(candidatos.map(c => [c.id, c.sim]));
|
|
141
|
+
// RM-038 (D7): o vetor so ranqueia o que o indice cobre; menos que o universo e dito aqui.
|
|
142
|
+
saida.coberturaDoIndice = { coerentes: v.ranking.length, universo: buscadas.length };
|
|
143
|
+
const aviso = (0, indice_vetorial_1.avisoDeCobertura)(v.ranking.length, buscadas, v.alvo, o.colecao ? ` na colecao ${o.colecao}` : '');
|
|
144
|
+
if (aviso)
|
|
145
|
+
detalhes.push(`vetor: ${aviso}`);
|
|
131
146
|
}
|
|
132
147
|
}
|
|
133
148
|
if (o.modo !== 'vetor') {
|
|
149
|
+
let ftsFalhou = false;
|
|
134
150
|
try {
|
|
135
151
|
if (!o.buscarTexto)
|
|
136
152
|
throw new Error('memory.transport.fts');
|
|
137
|
-
// A ponte ja
|
|
138
|
-
|
|
153
|
+
// A ponte ja restringe ao universo; a intersecao vale de novo aqui e a quantidade que sobra e declarada (D5).
|
|
154
|
+
const ids = [...new Set(o.buscarTexto(o.tenant, o.texto))];
|
|
155
|
+
saida.ftsForaDoUniverso = ids.filter(id => !doUniverso.has(id)).length;
|
|
156
|
+
if (saida.ftsForaDoUniverso)
|
|
157
|
+
detalhes.push(`fts: ${saida.ftsForaDoUniverso} id(s) fora do universo da busca descartado(s)`);
|
|
158
|
+
saida.listas.fts = ids.filter(id => porId.has(id));
|
|
139
159
|
}
|
|
140
160
|
catch (erro) {
|
|
141
|
-
|
|
161
|
+
ftsFalhou = true;
|
|
162
|
+
const codigo = (0, indice_vetorial_1.codigoDaFalha)(erro, 'memory.transport.fts');
|
|
142
163
|
detalhes.push(`fts: ${codigo}`);
|
|
143
164
|
if (o.modo === 'fts' || saida.origem === 'nenhum')
|
|
144
165
|
saida.motivo = saida.motivo ?? codigo;
|
|
145
166
|
}
|
|
146
|
-
if (saida.origem === 'nenhum' && !
|
|
167
|
+
if (saida.origem === 'nenhum' && !ftsFalhou)
|
|
147
168
|
saida.origem = 'fts';
|
|
148
169
|
}
|
|
149
170
|
const scores = fundirRrf([saida.listas.vetor, saida.listas.fts]);
|
|
@@ -159,3 +180,22 @@ function buscarPorSignificado(o) {
|
|
|
159
180
|
saida.detalhe = detalhes.join('; ');
|
|
160
181
|
return saida;
|
|
161
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Texto de `ork memory search --texto`. Revisao de 03/10 (RM-038): o detalhe sai tambem sem motivo; o
|
|
185
|
+
* id do FTS fora do universo e o aviso de cobertura vivem nele, e o texto os perdia na busca que deu certo.
|
|
186
|
+
*/
|
|
187
|
+
function textoDaBuscaPorSignificado(r) {
|
|
188
|
+
const linhas = [`Busca por significado (NAO deterministica; modo ${r.modo}, origem ${r.origem}${r.modeloUsado ? ` ${r.modeloUsado}` : ''})`];
|
|
189
|
+
if (r.motivo)
|
|
190
|
+
linhas.push(` motivo: ${r.motivo}${r.detalhe ? `; ${r.detalhe}` : ''}`);
|
|
191
|
+
else if (r.detalhe)
|
|
192
|
+
linhas.push(` detalhe: ${r.detalhe}`);
|
|
193
|
+
linhas.push('');
|
|
194
|
+
r.resultados.forEach((e, i) => {
|
|
195
|
+
const sim = e.similaridade === null ? '' : ` similaridade ${e.similaridade}`;
|
|
196
|
+
linhas.push(` ${i + 1}. [${e.collection}] ${e.id} score ${e.score} ${e.fontes.join('+')}${sim}`);
|
|
197
|
+
linhas.push(` ${e.resumo}`);
|
|
198
|
+
});
|
|
199
|
+
linhas.push('', ` ${r.resultados.length} resultado(s); busca por tag continua em ork memory search --tags`);
|
|
200
|
+
return linhas.join('\n');
|
|
201
|
+
}
|