@orkastery/cli 0.5.3 → 0.5.6
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/README.md +158 -0
- package/adapters/claude-code/mods/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
- package/adapters/openclaw/README.md +2 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/dist/adapters/claude-bg.js +77 -13
- package/dist/adapters/codex.js +10 -1
- package/dist/agents-md.js +24 -15
- package/dist/auditrun.js +17 -2
- package/dist/board.js +4 -3
- package/dist/branch-de-estado.js +23 -1
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +13 -7
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +3 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +36 -1
- package/dist/conducao-sentinela.js +388 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +122 -30
- package/dist/conhecimento.js +490 -0
- package/dist/docs.js +22 -8
- package/dist/doctor-pulse-cron.js +182 -27
- package/dist/doctor-rede.js +18 -17
- package/dist/doctor.js +115 -111
- package/dist/entrega-pr.js +22 -21
- package/dist/escopo-escrita.js +10 -1
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +38 -8
- package/dist/fabrica-publicar.js +3 -1
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +1 -1
- package/dist/gates.js +3 -28
- package/dist/handoff.js +10 -6
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-resumo.js +29 -3
- package/dist/hitl-tempo-parado.js +4 -4
- package/dist/hitl.js +29 -33
- package/dist/horario.js +14 -7
- package/dist/hosts.js +52 -1
- package/dist/impedimento.js +108 -14
- package/dist/index.js +623 -521
- package/dist/indice.js +7 -1
- package/dist/init.js +43 -47
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +92 -21
- package/dist/intelligence-graph-contexto.js +56 -17
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/ledger.js +15 -1
- package/dist/liveness.js +6 -3
- package/dist/locale-ajuda.js +761 -0
- package/dist/locale-gerados.js +160 -0
- package/dist/locale-master.js +99 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-pulse.js +211 -0
- package/dist/locale-retry.js +385 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-ship.js +494 -0
- package/dist/locale-thread.js +277 -0
- package/dist/locale-verify.js +125 -0
- package/dist/locale.js +714 -0
- package/dist/manifest.js +129 -3
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +4 -6
- package/dist/master.js +21 -24
- package/dist/mcp-artifacts.js +11 -2
- package/dist/mcp-git.js +23 -6
- package/dist/mcp-grafo.js +7 -4
- package/dist/mcp-server.js +8 -2
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +5 -2
- package/dist/noctum-arco.js +911 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-envelope.js +810 -0
- package/dist/noctum-execucao.js +304 -0
- package/dist/noctum-fila.js +280 -0
- package/dist/ocupacao.js +29 -2
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +9 -2
- package/dist/orquestracao.js +13 -12
- package/dist/parado-no-condutor.js +34 -9
- package/dist/phase.js +94 -39
- package/dist/policies.js +4 -4
- package/dist/portfolio.js +4 -2
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +127 -4
- package/dist/prompts.js +6 -2
- package/dist/pulse-delivery.js +13 -2
- package/dist/pulse.js +52 -19
- package/dist/ratelimit.js +4 -2
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-status.js +3 -2
- package/dist/retry.js +232 -148
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +129 -33
- package/dist/runtime-context.js +181 -3
- package/dist/runtimes.js +2 -1
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +7 -1
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +1 -1
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +84 -81
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +153 -82
- package/dist/util.js +8 -1
- package/dist/verify.js +141 -52
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +165 -0
- package/dist/worktree.js +20 -0
- package/dist/yaml.js +27 -8
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// O papel da sessao (RM-059, D4 do plano dos mods). Modulo puro: so dados entram e saem, para o
|
|
2
|
+
// teste do nucleo transpila-lo sem o Claude Code. Nenhum `$` passa por aqui.
|
|
3
|
+
import type { OrkPapel } from '../types'
|
|
4
|
+
|
|
5
|
+
/** So o servidor MCP sem thread registra esta ferramenta (core/src/mcp-server.ts). */
|
|
6
|
+
export const FERRAMENTA_DO_DONO = 'mcp__orkastery__ork_request_decision'
|
|
7
|
+
export const MANIFESTO_DO_PROJETO = 'orkastery.yaml'
|
|
8
|
+
const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
9
|
+
|
|
10
|
+
export type EntradaDoPapel = {
|
|
11
|
+
/** O `ORK_DISPATCH_ID` que o despacho passa pelo `--settings`. */
|
|
12
|
+
dispatchId: string | undefined
|
|
13
|
+
/** A raiz da sessao tem `orkastery.yaml`. */
|
|
14
|
+
temManifesto: boolean
|
|
15
|
+
/** Os nomes das ferramentas que o modelo tem agora (`$.tool.list()`). */
|
|
16
|
+
ferramentas: readonly string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `fase` com `ORK_DISPATCH_ID`; `dono` sem ele, com `orkastery.yaml` na raiz e o servidor `orkastery`
|
|
21
|
+
* sem thread conectado; `dormente` no resto, quando nenhum gancho da ponte age alem do inicio.
|
|
22
|
+
*/
|
|
23
|
+
export function decidirPapel(e: EntradaDoPapel): OrkPapel {
|
|
24
|
+
if (typeof e.dispatchId === 'string' && e.dispatchId.trim() !== '') return 'fase'
|
|
25
|
+
if (e.temManifesto && e.ferramentas.includes(FERRAMENTA_DO_DONO)) return 'dono'
|
|
26
|
+
return 'dormente'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A thread do despacho na sessao de fase, so com id valido; `null` fora dela. */
|
|
30
|
+
export function threadDoDespacho(papel: OrkPapel, thread: string | undefined): string | null {
|
|
31
|
+
if (papel !== 'fase' || typeof thread !== 'string') return null
|
|
32
|
+
return ID_DE_THREAD.test(thread) ? thread : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function idDeThreadValido(thread: unknown): thread is string {
|
|
36
|
+
return typeof thread === 'string' && ID_DE_THREAD.test(thread)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Junta pasta e nome com uma barra so, sem depender de `node:path` (o mod nao tem Node). */
|
|
40
|
+
export function juntar(pasta: string, nome: string): string {
|
|
41
|
+
return pasta.replace(/\/+$/, '') + '/' + nome.replace(/^\/+/, '')
|
|
42
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// A ponte dos mods do Orkastery (RM-059, onda 0). Acrescenta ao `$` o noun `$.ork`, que os outros mods
|
|
2
|
+
// do Orkastery usam para saber em que sessao estao, ler o nucleo, publicar na faixa e pedir caminhos
|
|
3
|
+
// vigiados, e guarda a regra de autoridade (T0.6): nenhum mod aprova gate, permissao, nota do MASTER,
|
|
4
|
+
// push ou tag pelo dono, nem fala como o dono. A logica fica nos modulos puros ao lado; aqui so as
|
|
5
|
+
// chamadas ao `$`, sempre escritas como `x.noun.metodo(...)`, como o validador exige.
|
|
6
|
+
import type { Register } from 'claude-code'
|
|
7
|
+
import type { Ork, OrkAssinatura, OrkContexto, OrkMaestro, OrkMostrado, OrkRetratoGuardado } from '../types'
|
|
8
|
+
import { comporFaixa, desenharFaixa, nomeDeModValido, trocarLinha } from './faixa'
|
|
9
|
+
import type { Achado, Autoridade } from './guarda'
|
|
10
|
+
import {
|
|
11
|
+
avaliarAmbiente, avaliarArgv, avaliarCaminho, avaliarUses, ehModDoOrkastery, lerAutoridade, MARCA_DE_MOD, NOME_DA_PONTE,
|
|
12
|
+
} from './guarda'
|
|
13
|
+
import { CHAVE_NO_STORE, conferirMostrado, lerRegistro, registrarMostrado } from './mostrado'
|
|
14
|
+
import {
|
|
15
|
+
argsDoMaestro, argvDaConsulta, cacheValido, caminhosCobertos, chaveDoPedido, conferirPedidoDeRetrato, FERRAMENTA_DO_RETRATO,
|
|
16
|
+
guardar, interpretarCli, interpretarMcp, raizPeloGit, SERVIDOR, subirAteOEstado, TETO_DO_CLI_MS,
|
|
17
|
+
} from './nucleo'
|
|
18
|
+
import { decidirPapel, juntar, MANIFESTO_DO_PROJETO, threadDoDespacho } from './papel'
|
|
19
|
+
import { caminhosPadrao, fundirCaminhos, idsDeThread, ledgerDaThread, ledgersRecentes, pastaDasThreads, registrarPedido } from './vigia'
|
|
20
|
+
|
|
21
|
+
const RETRATOS = { plugin: 'ork-ponte', key: 'retratos' } as const
|
|
22
|
+
const FAIXA = { plugin: 'ork-ponte', key: 'faixa' } as const
|
|
23
|
+
const VIGIAS = { plugin: 'ork-ponte', key: 'vigias' } as const
|
|
24
|
+
const TENTATIVAS_DE_ESCRITA = 5
|
|
25
|
+
|
|
26
|
+
/** O que os ganchos da ponte usam do noun, montado no `engine.create` de cada carga. */
|
|
27
|
+
type Interno = {
|
|
28
|
+
contexto: () => Promise<OrkContexto>
|
|
29
|
+
raizDoEstado: () => Promise<string | null>
|
|
30
|
+
ledgersRecentes: (raiz: string) => Promise<string[]>
|
|
31
|
+
}
|
|
32
|
+
let interno: Interno | undefined
|
|
33
|
+
/** O `classic.SessionStart` desta carga ja respondeu os `watchPaths`. */
|
|
34
|
+
let inicioRespondido = false
|
|
35
|
+
|
|
36
|
+
// A guarda (T0.6). A lista de autoridade e lida do disco uma vez por carga (D6 da thread): `null` quando
|
|
37
|
+
// falta ou nao tem o formato, e entao tudo que a guarda cobre fecha para os mods do Orkastery.
|
|
38
|
+
let autoridade: Autoridade | null | undefined
|
|
39
|
+
let motivoSemLista = ''
|
|
40
|
+
let raizDaPonte = ''
|
|
41
|
+
/** Mods do Orkastery sem prefixo `ork-`, conhecidos pelo `orkastery-mod.json` no `plugin.register`. */
|
|
42
|
+
const conhecidos = new Set<string>()
|
|
43
|
+
|
|
44
|
+
function pai(dir: string): string {
|
|
45
|
+
const sem = dir.replace(/\/+$/, '')
|
|
46
|
+
const i = sem.lastIndexOf('/')
|
|
47
|
+
return i <= 0 ? '/' : sem.slice(0, i)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A lista ao lado dos mods e, na falta dela, dentro da pasta da ponte (contrato para a distribuicao). */
|
|
51
|
+
async function obterAutoridade(ler: (caminho: string) => Promise<unknown>): Promise<Autoridade | null> {
|
|
52
|
+
if (autoridade !== undefined) return autoridade
|
|
53
|
+
const candidatos = [juntar(pai(raizDaPonte), 'autoridade.json'), juntar(raizDaPonte, 'autoridade.json')]
|
|
54
|
+
const motivos: string[] = []
|
|
55
|
+
for (const caminho of candidatos) {
|
|
56
|
+
try {
|
|
57
|
+
autoridade = lerAutoridade(String(await ler(caminho)))
|
|
58
|
+
return autoridade
|
|
59
|
+
} catch (erro) {
|
|
60
|
+
motivos.push(`${caminho}: ${erro instanceof Error ? erro.message : String(erro)}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
motivoSemLista = motivos.join('; ').slice(0, 400)
|
|
64
|
+
autoridade = null
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const recusa = (a: Achado) => `ork-ponte: ${a.regra}: ${a.porque}. Mod do Orkastery nao age pelo dono; ` +
|
|
69
|
+
'registre uma decisao autonoma pelo nucleo ou leve a pergunta ao dono pelo dialogo nativo'
|
|
70
|
+
const semLista = () => `ork-ponte: lista de autoridade ausente ou invalida (${motivoSemLista || 'nao lida'}); ` +
|
|
71
|
+
'a guarda fecha para os mods do Orkastery'
|
|
72
|
+
const ehMod = (nome: unknown) => ehModDoOrkastery(nome, conhecidos)
|
|
73
|
+
|
|
74
|
+
export const register: Register = on => {
|
|
75
|
+
on('engine.create', async ($, e, next) => {
|
|
76
|
+
const built = await next(e)
|
|
77
|
+
|
|
78
|
+
const lerContexto = async (): Promise<OrkContexto> => {
|
|
79
|
+
const dispatchId = await built.env.get('ORK_DISPATCH_ID')
|
|
80
|
+
const raiz = await built.session.root()
|
|
81
|
+
let temManifesto = false
|
|
82
|
+
let ferramentas: string[] = []
|
|
83
|
+
if (dispatchId === undefined || dispatchId.trim() === '') {
|
|
84
|
+
temManifesto = await built.fs.exists(juntar(raiz, MANIFESTO_DO_PROJETO))
|
|
85
|
+
if (temManifesto) ferramentas = (await built.tool.list()).map(t => t.name)
|
|
86
|
+
}
|
|
87
|
+
const papel = decidirPapel({ dispatchId, temManifesto, ferramentas })
|
|
88
|
+
const thread = threadDoDespacho(papel, await built.env.get('ORK_DISPATCH_THREAD'))
|
|
89
|
+
return { papel, thread, raiz }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A raiz do estado (a pasta com `orkastery.yaml` e `.orkastery/`), achada uma vez por carga.
|
|
93
|
+
let raizDoEstado: string | null | undefined
|
|
94
|
+
const acharRaizDoEstado = async (): Promise<string | null> => {
|
|
95
|
+
if (raizDoEstado !== undefined) return raizDoEstado
|
|
96
|
+
const raiz = await built.session.root()
|
|
97
|
+
let achada = await subirAteOEstado(raiz, caminho => built.fs.exists(caminho))
|
|
98
|
+
if (achada === null) {
|
|
99
|
+
const git = await built.process.run(['git', 'rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
100
|
+
{ cwd: raiz, timeoutMs: 5000 }).catch(() => undefined)
|
|
101
|
+
const candidata = git && git.exitCode === 0 ? raizPeloGit(git.stdout) : null
|
|
102
|
+
if (candidata !== null && await built.fs.exists(juntar(candidata, '.orkastery'))) achada = candidata
|
|
103
|
+
}
|
|
104
|
+
raizDoEstado = achada
|
|
105
|
+
return achada
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const assinar = async (caminhos: readonly string[]): Promise<OrkAssinatura> => {
|
|
109
|
+
const assinatura: OrkAssinatura = []
|
|
110
|
+
for (const caminho of caminhos) {
|
|
111
|
+
const s = await built.fs.stat(caminho).catch(() => undefined)
|
|
112
|
+
assinatura.push(s ? { caminho, tamanho: s.size, mtimeMs: s.mtimeMs } : { caminho, ausente: true })
|
|
113
|
+
}
|
|
114
|
+
return assinatura
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const lerLedgersRecentes = async (raiz: string): Promise<string[]> => {
|
|
118
|
+
const entradas = await built.fs.list(pastaDasThreads(raiz)).catch(() => [])
|
|
119
|
+
const ledgers: Array<{ caminho: string; mtimeMs?: number }> = []
|
|
120
|
+
for (const id of idsDeThread(entradas).slice(0, 400)) {
|
|
121
|
+
const caminho = ledgerDaThread(raiz, id)
|
|
122
|
+
const s = await built.fs.stat(caminho).catch(() => undefined)
|
|
123
|
+
ledgers.push(s ? { caminho, mtimeMs: s.mtimeMs } : { caminho })
|
|
124
|
+
}
|
|
125
|
+
return ledgersRecentes(ledgers)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const registrar = async (m: OrkMostrado): Promise<void> => {
|
|
129
|
+
const agora = new Date(await built.clock.now()).toISOString()
|
|
130
|
+
await built.store.set(CHAVE_NO_STORE, registrarMostrado(await built.store.get(CHAVE_NO_STORE), m, agora))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const ork: Ork = {
|
|
134
|
+
papel: async () => (await lerContexto()).papel,
|
|
135
|
+
contexto: async () => lerContexto(),
|
|
136
|
+
retrato: async bruto => {
|
|
137
|
+
const pedido = conferirPedidoDeRetrato(bruto)
|
|
138
|
+
const chave = chaveDoPedido(pedido)
|
|
139
|
+
const guardados = await built.state.get(RETRATOS)
|
|
140
|
+
const anterior = guardados.value?.[chave]
|
|
141
|
+
if (anterior !== undefined) {
|
|
142
|
+
const agora = await built.clock.now()
|
|
143
|
+
if (cacheValido(anterior, await assinar(anterior.caminhos), agora)) {
|
|
144
|
+
return { fonte: 'cache', lidoEm: anterior.lidoEm, retrato: anterior.retrato as OrkMaestro }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let retrato: unknown
|
|
148
|
+
let fonte: 'mcp' | 'cli'
|
|
149
|
+
try {
|
|
150
|
+
retrato = interpretarMcp(await built.mcp.call(SERVIDOR, FERRAMENTA_DO_RETRATO, argsDoMaestro(pedido)))
|
|
151
|
+
fonte = 'mcp'
|
|
152
|
+
} catch {
|
|
153
|
+
const argv = argvDaConsulta({ consulta: 'maestro', ...pedido })
|
|
154
|
+
retrato = interpretarCli(argv, await built.process.run(argv, { timeoutMs: TETO_DO_CLI_MS }))
|
|
155
|
+
fonte = 'cli'
|
|
156
|
+
}
|
|
157
|
+
const lidoEm = await built.clock.now()
|
|
158
|
+
// O `stat` sai depois da leitura: uma mudanca no meio fica no cache ate o teto de 60 s.
|
|
159
|
+
const caminhos = caminhosCobertos(await acharRaizDoEstado(), pedido, retrato)
|
|
160
|
+
if (caminhos.length > 0) {
|
|
161
|
+
const entrada: OrkRetratoGuardado = { fonte, lidoEm, retrato, caminhos, assinatura: await assinar(caminhos) }
|
|
162
|
+
// Escrita recusada (por exemplo, dentro de um desenho) so deixa de guardar.
|
|
163
|
+
await built.state.set(RETRATOS, guardar(guardados.value, chave, entrada), { ifVersion: guardados.version })
|
|
164
|
+
.catch(() => undefined)
|
|
165
|
+
}
|
|
166
|
+
return { fonte, lidoEm, retrato: retrato as OrkMaestro }
|
|
167
|
+
},
|
|
168
|
+
ler: async pedido => {
|
|
169
|
+
const argv = argvDaConsulta(pedido)
|
|
170
|
+
return interpretarCli(argv, await built.process.run(argv, { timeoutMs: TETO_DO_CLI_MS }))
|
|
171
|
+
},
|
|
172
|
+
faixa: async ({ mod, texto }) => {
|
|
173
|
+
if (!nomeDeModValido(mod)) throw new Error('ork-ponte: nome de mod invalido')
|
|
174
|
+
if (texto !== undefined && typeof texto !== 'string') throw new Error('ork-ponte: texto da faixa precisa ser texto')
|
|
175
|
+
let publicada = false
|
|
176
|
+
for (let i = 0; i < TENTATIVAS_DE_ESCRITA; i++) {
|
|
177
|
+
const atual = await built.state.get(FAIXA)
|
|
178
|
+
const linhas = trocarLinha(atual.value, mod, texto, await built.clock.now())
|
|
179
|
+
const escrita = await built.state.set(FAIXA, linhas, { ifVersion: atual.version })
|
|
180
|
+
if (escrita.isSet) { publicada = linhas.some(l => l.mod === mod); break }
|
|
181
|
+
}
|
|
182
|
+
if (publicada && texto !== undefined && (await lerContexto()).papel === 'dono') {
|
|
183
|
+
await registrar({ mod, canal: 'faixa', texto })
|
|
184
|
+
}
|
|
185
|
+
return { publicada }
|
|
186
|
+
},
|
|
187
|
+
vigiar: async ({ mod, caminhos }) => {
|
|
188
|
+
for (let i = 0; i < TENTATIVAS_DE_ESCRITA; i++) {
|
|
189
|
+
const atual = await built.state.get(VIGIAS)
|
|
190
|
+
const pedidos = registrarPedido(atual.value, mod, caminhos)
|
|
191
|
+
const escrita = await built.state.set(VIGIAS, pedidos, { ifVersion: atual.version })
|
|
192
|
+
if (escrita.isSet) return { caminhos: pedidos[mod] ?? [], valeNoProximoInicio: inicioRespondido }
|
|
193
|
+
}
|
|
194
|
+
throw new Error('ork-ponte: o pedido de caminhos nao foi guardado')
|
|
195
|
+
},
|
|
196
|
+
mostrado: async pedido => registrar(conferirMostrado(pedido)),
|
|
197
|
+
mostrados: async () => lerRegistro(await built.store.get(CHAVE_NO_STORE)),
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
interno = { contexto: lerContexto, raizDoEstado: acharRaizDoEstado, ledgersRecentes: lerLedgersRecentes }
|
|
201
|
+
raizDaPonte = built.plugin.root
|
|
202
|
+
return { ...built, ork }
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// So a ponte responde `watchPaths` entre os mods do Orkastery: o que veio de baixo, os padroes e os pedidos.
|
|
206
|
+
on('classic.SessionStart', async ($, e, next) => {
|
|
207
|
+
const resultado = await next(e)
|
|
208
|
+
if (interno === undefined) return resultado
|
|
209
|
+
const contexto = await interno.contexto()
|
|
210
|
+
if (contexto.papel === 'dormente') return resultado
|
|
211
|
+
const raiz = await interno.raizDoEstado()
|
|
212
|
+
const recentes = contexto.papel === 'dono' && raiz !== null ? await interno.ledgersRecentes(raiz) : []
|
|
213
|
+
const pedidos = (await $.state.get(VIGIAS)).value
|
|
214
|
+
inicioRespondido = true
|
|
215
|
+
return { ...resultado, watchPaths: fundirCaminhos(resultado.watchPaths, caminhosPadrao(contexto.papel, raiz, contexto.thread, recentes), pedidos) }
|
|
216
|
+
}).catch(($, e, next) => next(e))
|
|
217
|
+
|
|
218
|
+
// A faixa acima do prompt: uma linha por mod; sem linha, a faixa fica com quem esta abaixo.
|
|
219
|
+
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
|
|
220
|
+
if (e.props.hasSurvey) return next(e)
|
|
221
|
+
const linhas = comporFaixa((await $.state.get(FAIXA)).value, e.props.bodyColumns, e.props.maxRows)
|
|
222
|
+
if (linhas.length === 0) return next(e)
|
|
223
|
+
const { Box, Text } = $.ui.resolve(e)
|
|
224
|
+
return desenharFaixa({ Box, Text }, linhas)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// A guarda em tempo de execucao (T0.6). Cada chamada de `$` e um evento que passa por todos os ganchos,
|
|
228
|
+
// de qualquer camada, menos o de quem chamou: a ponte ve a chamada do mod onde quer que ele esteja na
|
|
229
|
+
// cadeia. So um gancho mais externo que respondesse sem `next` a contornaria, e por isso esses eventos
|
|
230
|
+
// ficam reservados a ponte (o `plugin.register` abaixo recusa o mod que os registra). Cada gancho tem
|
|
231
|
+
// `.catch` que fecha: sem poder chamar o `$`, o `.catch` decide so com o evento e a lista ja lida.
|
|
232
|
+
|
|
233
|
+
// A lista tambem e lida no inicio, para a sessao saber logo quando a guarda esta fechada.
|
|
234
|
+
on('session.start', async ($, e, next) => {
|
|
235
|
+
const lida = await obterAutoridade(caminho => $.fs.read(caminho))
|
|
236
|
+
if (lida === null) $.ui.status(semLista())
|
|
237
|
+
return next(e)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
on('process.run', async ($, e, next) => {
|
|
241
|
+
if (!ehMod(next.origin.plugin)) return next(e)
|
|
242
|
+
const a = await obterAutoridade(caminho => $.fs.read(caminho))
|
|
243
|
+
if (a === null) return { deny: semLista() }
|
|
244
|
+
const achado = avaliarArgv(a, e.argv, e.init?.stdin)
|
|
245
|
+
return achado ? { deny: recusa(achado) } : next(e)
|
|
246
|
+
}).catch(($, e, next) => {
|
|
247
|
+
if (next.called || !ehMod(next.origin.plugin)) return next(e)
|
|
248
|
+
if (!autoridade) return { deny: semLista() }
|
|
249
|
+
const achado = avaliarArgv(autoridade, e.argv, e.init?.stdin)
|
|
250
|
+
return achado ? { deny: recusa(achado) } : next(e)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
on('process.spawn', async function* ($, e, next) {
|
|
254
|
+
if (ehMod(next.origin.plugin)) {
|
|
255
|
+
const a = await obterAutoridade(caminho => $.fs.read(caminho))
|
|
256
|
+
if (a === null) return { deny: semLista() }
|
|
257
|
+
const achado = avaliarArgv(a, e.argv, e.input)
|
|
258
|
+
if (achado) return { deny: recusa(achado) }
|
|
259
|
+
}
|
|
260
|
+
return yield* next(e)
|
|
261
|
+
}).catch(async function* ($, e, next) {
|
|
262
|
+
if (next.called || !ehMod(next.origin.plugin)) return yield* next(e)
|
|
263
|
+
if (!autoridade) return { deny: semLista() }
|
|
264
|
+
const achado = avaliarArgv(autoridade, e.argv, e.input)
|
|
265
|
+
if (achado) return { deny: recusa(achado) }
|
|
266
|
+
return yield* next(e)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Mod do Orkastery que hooka evento proibido ou reservado, chama `$.prompt.submit` ou le `ORK_HITL_*`
|
|
270
|
+
// nem carrega. O nome `ork-ponte` fica reservado a esta pasta.
|
|
271
|
+
on('plugin.register', async ($, e, next) => {
|
|
272
|
+
if (e.root === raizDaPonte) return next(e)
|
|
273
|
+
if (e.name === NOME_DA_PONTE) return { refuse: `ork-ponte: o nome ${NOME_DA_PONTE} e reservado a ponte do Orkastery` }
|
|
274
|
+
const marcado = e.name.startsWith('ork-') || await $.fs.exists(juntar(e.root, MARCA_DE_MOD)).catch(() => false)
|
|
275
|
+
if (!marcado) return next(e)
|
|
276
|
+
conhecidos.add(e.name)
|
|
277
|
+
const a = await obterAutoridade(caminho => $.fs.read(caminho))
|
|
278
|
+
if (a === null) return { refuse: semLista() }
|
|
279
|
+
const achado = avaliarUses(a, e.uses)
|
|
280
|
+
return achado ? { refuse: recusa(achado) } : next(e)
|
|
281
|
+
}).catch(($, e, next) => (e.root !== raizDaPonte && (e.name.startsWith('ork-') || conhecidos.has(e.name))
|
|
282
|
+
? { refuse: 'ork-ponte: a guarda falhou ao conferir o mod do Orkastery' } : next(e)))
|
|
283
|
+
|
|
284
|
+
// Texto enviado como se fosse do dono, vindo de mod do Orkastery, nao entra.
|
|
285
|
+
on('prompt.submit', ($, e, next) => {
|
|
286
|
+
const mod = e.origin.kind === 'plugin' && 'name' in e.origin ? e.origin.name : undefined
|
|
287
|
+
if (ehMod(mod)) {
|
|
288
|
+
return { drop: `ork-ponte: o mod ${String(mod)} nao fala pelo dono; use a faixa, uma notificacao ou a decisao autonoma do nucleo` }
|
|
289
|
+
}
|
|
290
|
+
return next(e)
|
|
291
|
+
}).catch(($, e, next) => (next.called ? next(e)
|
|
292
|
+
: ehMod(e.origin.kind === 'plugin' && 'name' in e.origin ? e.origin.name : undefined)
|
|
293
|
+
? { drop: 'ork-ponte: a guarda falhou; texto de mod nao entra' } : next(e)))
|
|
294
|
+
|
|
295
|
+
on('session.append', ($, e, next) => {
|
|
296
|
+
const mod = e.origin.kind === 'plugin' && 'name' in e.origin ? e.origin.name : undefined
|
|
297
|
+
if (ehMod(mod) && e.message.type === 'user') {
|
|
298
|
+
return { deny: `ork-ponte: o mod ${String(mod)} nao grava linha de usuario; o modelo a leria como palavra do dono` }
|
|
299
|
+
}
|
|
300
|
+
return next(e)
|
|
301
|
+
}).catch(($, e, next) => (next.called ? next(e)
|
|
302
|
+
: ehMod(e.origin.kind === 'plugin' && 'name' in e.origin ? e.origin.name : undefined) && e.message.type === 'user'
|
|
303
|
+
? { deny: 'ork-ponte: a guarda falhou; linha de usuario de mod nao entra' } : next(e)))
|
|
304
|
+
|
|
305
|
+
// A propria ponte le o ambiente e a lista do disco: so os outros mods do Orkastery passam por aqui.
|
|
306
|
+
on('env.get', async ($, e, next) => {
|
|
307
|
+
const origem = next.origin.plugin
|
|
308
|
+
if (origem === NOME_DA_PONTE || !ehMod(origem)) return next(e)
|
|
309
|
+
const a = await obterAutoridade(caminho => $.fs.read(caminho))
|
|
310
|
+
if (a === null) return { deny: semLista() }
|
|
311
|
+
const achado = avaliarAmbiente(a, e.name)
|
|
312
|
+
return achado ? { deny: recusa(achado) } : next(e)
|
|
313
|
+
}).catch(($, e, next) => {
|
|
314
|
+
if (next.called || next.origin.plugin === NOME_DA_PONTE || !ehMod(next.origin.plugin)) return next(e)
|
|
315
|
+
const achado = autoridade ? avaliarAmbiente(autoridade, e.name) : null
|
|
316
|
+
return autoridade && !achado ? next(e) : { deny: achado ? recusa(achado) : semLista() }
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
on('fs.read', async ($, e, next) => {
|
|
320
|
+
const origem = next.origin.plugin
|
|
321
|
+
if (origem === NOME_DA_PONTE || !ehMod(origem)) return next(e)
|
|
322
|
+
const a = await obterAutoridade(caminho => $.fs.read(caminho))
|
|
323
|
+
if (a === null) return { deny: semLista() }
|
|
324
|
+
const achado = avaliarCaminho(a, e.path)
|
|
325
|
+
return achado ? { deny: recusa(achado) } : next(e)
|
|
326
|
+
}).catch(($, e, next) => {
|
|
327
|
+
if (next.called || next.origin.plugin === NOME_DA_PONTE || !ehMod(next.origin.plugin)) return next(e)
|
|
328
|
+
const achado = autoridade ? avaliarCaminho(autoridade, e.path) : null
|
|
329
|
+
return autoridade && !achado ? next(e) : { deny: achado ? recusa(achado) : semLista() }
|
|
330
|
+
})
|
|
331
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Os caminhos vigiados dos mods do Orkastery (RM-059, T0.4; D7 do plano dos mods e D12 da thread). Os
|
|
2
|
+
// `watchPaths` de varios ganchos de `classic.SessionStart` se fundem e a ultima escrita vence: por isso so
|
|
3
|
+
// a ponte responde, juntando o que veio de baixo, os padroes do papel e os pedidos dos mods. Modulo puro.
|
|
4
|
+
import type { OrkPapel } from '../types'
|
|
5
|
+
|
|
6
|
+
export const TETO_DE_CAMINHOS = 64
|
|
7
|
+
export const TETO_DE_LEDGERS_DO_DONO = 48
|
|
8
|
+
export const TETO_POR_MOD = 16
|
|
9
|
+
const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
10
|
+
const NOME_DE_MOD = /^[a-z0-9][a-z0-9-]{0,63}$/
|
|
11
|
+
|
|
12
|
+
function juntar(pasta: string, nome: string): string {
|
|
13
|
+
return pasta.replace(/\/+$/, '') + '/' + nome
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function pastaDasThreads(raizDoEstado: string): string {
|
|
17
|
+
return juntar(raizDoEstado, '.orkastery/threads')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ledgerDaThread(raizDoEstado: string, thread: string): string {
|
|
21
|
+
return juntar(pastaDasThreads(raizDoEstado), `${thread}/ledger.jsonl`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Ids de thread validos entre as entradas da pasta das threads (so pastas). */
|
|
25
|
+
export function idsDeThread(entradas: ReadonlyArray<{ name: string; kind: string }>): string[] {
|
|
26
|
+
return entradas.filter(e => e.kind === 'dir' && ID_DE_THREAD.test(e.name)).map(e => e.name).sort()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Os ledgers mais recentes por `mtimeMs`, com teto; ausente (sem `mtimeMs`) fica de fora. */
|
|
30
|
+
export function ledgersRecentes(ledgers: ReadonlyArray<{ caminho: string; mtimeMs?: number }>, teto = TETO_DE_LEDGERS_DO_DONO): string[] {
|
|
31
|
+
return ledgers.filter(l => typeof l.mtimeMs === 'number')
|
|
32
|
+
.sort((a, b) => (b.mtimeMs as number) - (a.mtimeMs as number) || (a.caminho < b.caminho ? -1 : 1))
|
|
33
|
+
.slice(0, teto).map(l => l.caminho)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** O que a ponte vigia por padrao: o ledger da thread na fase; a pasta e os ledgers recentes no dono. */
|
|
37
|
+
export function caminhosPadrao(papel: OrkPapel, raizDoEstado: string | null, thread: string | null, recentes: readonly string[]): string[] {
|
|
38
|
+
if (raizDoEstado === null) return []
|
|
39
|
+
if (papel === 'fase') return thread !== null && ID_DE_THREAD.test(thread) ? [ledgerDaThread(raizDoEstado, thread)] : []
|
|
40
|
+
if (papel === 'dono') return [pastaDasThreads(raizDoEstado), ...recentes]
|
|
41
|
+
return []
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Confere e guarda o pedido de um mod: caminhos absolutos, sem `..`, com teto por mod. */
|
|
45
|
+
export function registrarPedido(pedidos: Readonly<Record<string, string[]>> | undefined, mod: unknown,
|
|
46
|
+
caminhos: unknown): Record<string, string[]> {
|
|
47
|
+
if (typeof mod !== 'string' || !NOME_DE_MOD.test(mod)) throw new Error('ork-ponte: nome de mod invalido')
|
|
48
|
+
if (!Array.isArray(caminhos)) throw new Error('ork-ponte: caminhos precisa ser uma lista')
|
|
49
|
+
const validos: string[] = []
|
|
50
|
+
for (const c of caminhos) {
|
|
51
|
+
if (typeof c !== 'string' || !c.startsWith('/') || c.split('/').includes('..')) throw new Error('ork-ponte: caminho vigiado precisa ser absoluto e sem ..')
|
|
52
|
+
if (!validos.includes(c)) validos.push(c)
|
|
53
|
+
}
|
|
54
|
+
if (validos.length > TETO_POR_MOD) throw new Error(`ork-ponte: no maximo ${TETO_POR_MOD} caminhos por mod`)
|
|
55
|
+
const todos = { ...(pedidos ?? {}) }
|
|
56
|
+
if (validos.length === 0) delete todos[mod]
|
|
57
|
+
else todos[mod] = validos
|
|
58
|
+
return todos
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* O `watchPaths` final: primeiro o que veio de baixo (nunca cortado, nao e da ponte), depois os padroes e
|
|
63
|
+
* os pedidos dos mods em ordem de nome, sem duplicata, ate o teto.
|
|
64
|
+
*/
|
|
65
|
+
export function fundirCaminhos(deBaixo: readonly string[] | undefined, padrao: readonly string[],
|
|
66
|
+
pedidos: Readonly<Record<string, readonly string[]>> | undefined, teto = TETO_DE_CAMINHOS): string[] {
|
|
67
|
+
const saida: string[] = []
|
|
68
|
+
for (const c of deBaixo ?? []) if (!saida.includes(c)) saida.push(c)
|
|
69
|
+
const limite = Math.max(teto, saida.length)
|
|
70
|
+
const nossos = [...padrao, ...Object.keys(pedidos ?? {}).sort().flatMap(m => (pedidos ?? {})[m] ?? [])]
|
|
71
|
+
for (const c of nossos) {
|
|
72
|
+
if (saida.length >= limite) break
|
|
73
|
+
if (!saida.includes(c)) saida.push(c)
|
|
74
|
+
}
|
|
75
|
+
return saida
|
|
76
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Contrato do noun `$.ork`, que a ponte acrescenta ao `$` no `engine.create` (RM-059, onda 0).
|
|
2
|
+
// Um mod do Orkastery lista `ork-ponte` em `dependencies` no proprio plugin.json e recebe estes tipos
|
|
3
|
+
// em `.claude-plugin/types/ork-ponte/`. Todo metodo recebe um objeto so: o host recusa noun com mais
|
|
4
|
+
// de um argumento. Sem import nem referencia, como o motor exige de um contrato. O noun e servico aberto
|
|
5
|
+
// a qualquer plugin carregado: o campo `mod` e o nome que quem chama declara, nao uma identidade; a
|
|
6
|
+
// autoridade de cada mod fica com a guarda da ponte, que olha a origem real de cada chamada de `$`.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* O papel da sessao: `dono` na sessao interativa do dono (servidor `orkastery` sem thread),
|
|
10
|
+
* `fase` na sessao despachada pelo nucleo (`ORK_DISPATCH_ID`), `dormente` fora do Orkastery.
|
|
11
|
+
*/
|
|
12
|
+
export type OrkPapel = 'dono' | 'fase' | 'dormente'
|
|
13
|
+
|
|
14
|
+
/** O que a ponte sabe da sessao, calculado a cada chamada (o MCP pode conectar depois do inicio). */
|
|
15
|
+
export type OrkContexto = {
|
|
16
|
+
papel: OrkPapel
|
|
17
|
+
/** A thread do despacho, so na sessao de fase e com id valido. */
|
|
18
|
+
thread: string | null
|
|
19
|
+
/** A raiz da sessao, absoluta. */
|
|
20
|
+
raiz: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** As secoes do retrato `ork.maestro-snapshot/v1` do nucleo. */
|
|
24
|
+
export type OrkSecao = 'portfolio' | 'demands' | 'threads' | 'sessions' | 'blockers' | 'leases' | 'retries' | 'hitl'
|
|
25
|
+
| 'ship' | 'master' | 'nextActions'
|
|
26
|
+
|
|
27
|
+
/** A lista fechada de consultas de leitura do CLI, todas com `--json` (D10). */
|
|
28
|
+
export type OrkConsulta = 'maestro' | 'thread.status' | 'thread.list' | 'conducao.status' | 'retry.plan' | 'retry.list'
|
|
29
|
+
| 'pulse' | 'orquestracao.status' | 'sessions.hitl' | 'roadmap.status' | 'accounts.esgotamentos' | 'decisao.placar'
|
|
30
|
+
|
|
31
|
+
/** O retrato inteiro, uma secao (com `offset`, padrao 0) ou o recorte de uma thread. */
|
|
32
|
+
export type OrkPedidoDeRetrato = {
|
|
33
|
+
secao?: OrkSecao
|
|
34
|
+
thread?: string
|
|
35
|
+
offset?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type OrkPedidoDeLeitura = {
|
|
39
|
+
consulta: OrkConsulta
|
|
40
|
+
/** Obrigatoria em `thread.status`, `conducao.status`, `retry.plan` e `decisao.placar`. */
|
|
41
|
+
thread?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A acao que o nucleo declara para um item, quando declara (projecao de `ork.maestro-snapshot/v1`). */
|
|
45
|
+
export type OrkAcaoDoMaestro = {
|
|
46
|
+
operation: string
|
|
47
|
+
available: boolean
|
|
48
|
+
reason: string
|
|
49
|
+
preconditions: string[]
|
|
50
|
+
readback: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type OrkItemDoMaestro = {
|
|
54
|
+
id: string
|
|
55
|
+
title: string
|
|
56
|
+
status: string
|
|
57
|
+
refs: Array<{ kind: string; id: string }>
|
|
58
|
+
facts: Record<string, string | number | boolean | null>
|
|
59
|
+
action?: OrkAcaoDoMaestro
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type OrkSecaoDoMaestro = {
|
|
63
|
+
state: 'available' | 'empty' | 'unavailable' | 'stale' | 'conflict'
|
|
64
|
+
source: string
|
|
65
|
+
observedAt: string
|
|
66
|
+
items: OrkItemDoMaestro[]
|
|
67
|
+
gaps: string[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Projecao minima do `ork.maestro-snapshot/v1`; o resto do objeto chega como o nucleo mandou. */
|
|
71
|
+
export type OrkMaestro = {
|
|
72
|
+
schema: 'ork.maestro-snapshot/v1'
|
|
73
|
+
observedAt: string
|
|
74
|
+
sections: Partial<Record<OrkSecao, OrkSecaoDoMaestro>>
|
|
75
|
+
gaps: string[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type OrkRetrato = {
|
|
79
|
+
/** De onde veio: o MCP do nucleo, a reserva pelo CLI ou o cache da ponte. */
|
|
80
|
+
fonte: 'mcp' | 'cli' | 'cache'
|
|
81
|
+
/** Quando a ponte leu, em ms desde a epoca (relogio do `$.clock`). */
|
|
82
|
+
lidoEm: number
|
|
83
|
+
retrato: OrkMaestro
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** O `stat` dos ledgers cobertos por um retrato guardado. */
|
|
87
|
+
export type OrkAssinatura = Array<{ caminho: string; tamanho: number; mtimeMs: number } | { caminho: string; ausente: true }>
|
|
88
|
+
|
|
89
|
+
export type OrkRetratoGuardado = {
|
|
90
|
+
fonte: 'mcp' | 'cli'
|
|
91
|
+
lidoEm: number
|
|
92
|
+
retrato: unknown
|
|
93
|
+
caminhos: string[]
|
|
94
|
+
assinatura: OrkAssinatura
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A linha que um mod publica na faixa. */
|
|
98
|
+
export type OrkLinhaDaFaixa = {
|
|
99
|
+
mod: string
|
|
100
|
+
texto: string
|
|
101
|
+
/** Quando foi publicada, em ms desde a epoca. */
|
|
102
|
+
em: number
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Onde um mod mostrou algo ao dono. */
|
|
106
|
+
export type OrkCanal = 'faixa' | 'painel' | 'notificacao' | 'toast' | 'status' | 'transcript'
|
|
107
|
+
|
|
108
|
+
/** O que um mod diz ter mostrado ao dono: `chave` agrupa o que e a mesma coisa (por exemplo, `hitl:<pedido>`). */
|
|
109
|
+
export type OrkMostrado = {
|
|
110
|
+
mod: string
|
|
111
|
+
canal: OrkCanal
|
|
112
|
+
texto: string
|
|
113
|
+
chave?: string
|
|
114
|
+
thread?: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** O registro `ork.mod-mostrado/v1` no `$.store` da ponte, para o nucleo deduplicar o Telegram depois. */
|
|
118
|
+
export type OrkRegistroDeMostrados = {
|
|
119
|
+
formato: 'ork.mod-mostrado/v1'
|
|
120
|
+
itens: Array<{
|
|
121
|
+
mod: string
|
|
122
|
+
canal: OrkCanal
|
|
123
|
+
chave: string | null
|
|
124
|
+
thread: string | null
|
|
125
|
+
texto: string
|
|
126
|
+
primeiraVez: string
|
|
127
|
+
ultimaVez: string
|
|
128
|
+
vezes: number
|
|
129
|
+
}>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type OrkVigia = {
|
|
133
|
+
/** Os caminhos que este mod pediu, como ficaram guardados. */
|
|
134
|
+
caminhos: string[]
|
|
135
|
+
/** O `SessionStart` desta sessao ja respondeu: o pedido so vale no proximo (`/clear`, retomada). */
|
|
136
|
+
valeNoProximoInicio: boolean
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type Ork = {
|
|
140
|
+
/** O papel desta sessao. */
|
|
141
|
+
papel: () => Promise<OrkPapel>
|
|
142
|
+
/** Papel, thread do despacho e raiz da sessao. */
|
|
143
|
+
contexto: () => Promise<OrkContexto>
|
|
144
|
+
/**
|
|
145
|
+
* O retrato do nucleo pelo `ork_maestro`, com reserva na consulta `maestro` do CLI (teto de 10 s).
|
|
146
|
+
* Volta do cache enquanto o `stat` dos ledgers cobertos nao muda, por no maximo 60 s.
|
|
147
|
+
*/
|
|
148
|
+
retrato: (pedido?: OrkPedidoDeRetrato) => Promise<OrkRetrato>
|
|
149
|
+
/** Uma consulta da lista fechada pelo CLI, com teto de 10 s; recusa o que estiver fora dela. */
|
|
150
|
+
ler: (pedido: OrkPedidoDeLeitura) => Promise<unknown>
|
|
151
|
+
/**
|
|
152
|
+
* Publica (ou tira, sem `texto`) a linha deste mod na faixa acima do prompt, que so a ponte desenha.
|
|
153
|
+
* Na sessao do dono, a linha publicada entra no registro do que foi mostrado.
|
|
154
|
+
*/
|
|
155
|
+
faixa: (pedido: { mod: string; texto?: string }) => Promise<{ publicada: boolean }>
|
|
156
|
+
/** Pede caminhos absolutos para o `watchPaths`, que so a ponte responde; sem caminhos, tira o pedido. */
|
|
157
|
+
vigiar: (pedido: { mod: string; caminhos: string[] }) => Promise<OrkVigia>
|
|
158
|
+
/** Registra o que o mod mostrou ao dono fora da faixa (painel, notificacao, toast, status). */
|
|
159
|
+
mostrado: (pedido: OrkMostrado) => Promise<void>
|
|
160
|
+
/** O registro do que os mods mostraram ao dono. */
|
|
161
|
+
mostrados: () => Promise<OrkRegistroDeMostrados>
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
declare module 'claude-code' {
|
|
165
|
+
interface EngineInterface {
|
|
166
|
+
ork: Ork
|
|
167
|
+
}
|
|
168
|
+
interface PluginState {
|
|
169
|
+
'ork-ponte': {
|
|
170
|
+
/** Os retratos guardados, por pedido. */
|
|
171
|
+
retratos: Record<string, OrkRetratoGuardado>
|
|
172
|
+
/** As linhas da faixa, uma por mod. */
|
|
173
|
+
faixa: OrkLinhaDaFaixa[]
|
|
174
|
+
/** Os caminhos que cada mod pediu para vigiar. */
|
|
175
|
+
vigias: Record<string, string[]>
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ork-portao",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Portão de Tarefas do Orkastery no Claude Code: na sessão de fase do GO, cada tarefa do quadro é uma tarefa do plano com touch_paths e verify, só fecha com a conferência do núcleo aprovada e, com agent teams no piloto, vai ao teammate ocioso com lease livre.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Orkastery contributors",
|
|
7
|
+
"url": "https://github.com/orkastery"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://orkastery.com/",
|
|
10
|
+
"repository": "https://github.com/orkastery/orkastery",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
13
|
+
"dependencies": ["ork-ponte"]
|
|
14
|
+
}
|