@softize/opus 8.8.1 → 8.9.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/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/bin/cli.mjs +45 -15
- package/bin/lib/create.mjs +5 -5
- package/bin/lib/init.mjs +20 -228
- package/bin/lib/materialize.mjs +273 -0
- package/bin/lib/mcp.mjs +3 -3
- package/bin/lib/postinstall.mjs +7 -4
- package/bin/lib/validate-skill.mjs +40 -0
- package/docs/code-style.md +4 -4
- package/docs/releasing.md +12 -14
- package/package.json +16 -14
- package/registry/git/pre-push +8 -0
- package/registry/git/pre-push.d/opus +9 -0
- package/registry/github/opus.yml +22 -0
- package/registry/instructions/opus.md +15 -0
- package/registry/skills/build-opus-ui/SKILL.md +39 -0
- package/registry/skills/build-opus-ui/agents/openai.yaml +4 -0
- package/registry/skills/build-opus-ui/references/evaluations.md +5 -0
- package/registry/skills/build-opus-ui/references/ui-patterns.md +8 -0
- package/registry/skills/create-opus-action/SKILL.md +43 -0
- package/registry/skills/create-opus-action/agents/openai.yaml +4 -0
- package/registry/skills/create-opus-action/references/contract-and-binding.md +12 -0
- package/registry/skills/create-opus-action/references/evaluations.md +5 -0
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +100 -0
- package/registry/skills/implement-opus-change/SKILL.md +43 -0
- package/registry/skills/implement-opus-change/agents/openai.yaml +4 -0
- package/registry/skills/implement-opus-change/references/evaluations.md +5 -0
- package/registry/skills/implement-opus-change/references/protocol-boundaries.md +11 -0
- package/registry/skills/test-opus-action/SKILL.md +38 -0
- package/registry/skills/test-opus-action/agents/openai.yaml +4 -0
- package/registry/skills/test-opus-action/references/evaluations.md +5 -0
- package/registry/skills/test-opus-action/references/harness.md +10 -0
- package/registry/skills/upgrade-opus/SKILL.md +37 -0
- package/registry/skills/upgrade-opus/agents/openai.yaml +4 -0
- package/registry/skills/upgrade-opus/references/evaluations.md +5 -0
- package/registry/skills/upgrade-opus/references/upgrade-checklist.md +8 -0
- package/registry/templates/app/src/domains/tasks/index.ts +1 -1
- package/src/ui/components/primitives/select.tsx +18 -6
- package/src/ui/docs/content/actions.md +1 -1
- package/src/ui/docs/content/cli.md +2 -2
- package/src/ui/docs/content/customization.md +1 -1
- package/src/ui/docs/content/data.md +1 -1
- package/src/ui/docs/content/microcopy.md +1 -1
- package/src/ui/docs/content/scroll-area.md +1 -1
- package/src/ui/docs/content/tokens.md +1 -1
- package/src/ui/docs/content/ui.md +1 -1
- package/src/ui/docs/doc-client.tsx +2 -2
- package/src/ui/theme.css +1 -1
- package/registry/hooks/hooks.json +0 -26
- package/registry/hooks/link-memory-on-start.mjs +0 -46
- package/registry/skills/create-action/SKILL.md +0 -49
- package/registry/skills/create-action/scaffold.mjs +0 -122
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-opus-action
|
|
3
|
+
description: Cria uma action Opus no split canônico de defineContract e bindAction, incluindo registro e validação. Use ao adicionar endpoint, comando, consulta ou operação de domínio em projeto baseado no Opus.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Criar action Opus
|
|
7
|
+
|
|
8
|
+
## Resultado
|
|
9
|
+
|
|
10
|
+
Criar contrato compartilhável e binding server-only que passam no `opus check` e ficam
|
|
11
|
+
registrados no runtime do projeto.
|
|
12
|
+
|
|
13
|
+
## Entradas
|
|
14
|
+
|
|
15
|
+
- nome `<resource>.<verb>` e `kind` (`simple`, `form`, `list` ou `view`);
|
|
16
|
+
- comportamento, autorização, input, output, erros e efeitos esperados;
|
|
17
|
+
- caminhos reais de `shared` e `api` adotados pelo repo.
|
|
18
|
+
|
|
19
|
+
## Procedimento
|
|
20
|
+
|
|
21
|
+
1. Rodar `node scripts/scaffold.mjs <resource> <verb> <kind>` a partir desta skill.
|
|
22
|
+
2. Colocar o contrato em módulo importável pelos consumidores e o binding no lado servidor.
|
|
23
|
+
3. Substituir os exemplos do scaffold por schemas, descrições e comportamento do domínio.
|
|
24
|
+
4. Declarar `requires` somente com `authorize` efetivo; `requires` sozinho não protege a action.
|
|
25
|
+
5. Exportar e registrar o binding no domínio/runtime existente.
|
|
26
|
+
6. Acionar `test-opus-action` para cobrir contrato, sucesso, erros e efeitos relevantes.
|
|
27
|
+
|
|
28
|
+
## Verificação
|
|
29
|
+
|
|
30
|
+
Rodar `opus check <escopo>`, typecheck e testes do domínio. Se o projeto gera manifest,
|
|
31
|
+
OpenAPI ou docs, regenerar e conferir o diff.
|
|
32
|
+
|
|
33
|
+
## Limites
|
|
34
|
+
|
|
35
|
+
- Não colocar banco, segredo ou driver no arquivo de contrato compartilhado.
|
|
36
|
+
- Não duplicar input/output em tipos manuais, cliente ou documentação paralela.
|
|
37
|
+
- Não criar autorização fictícia apenas para satisfazer o check.
|
|
38
|
+
|
|
39
|
+
## Recursos
|
|
40
|
+
|
|
41
|
+
- Leia [contrato e binding](references/contract-and-binding.md) quando houver dúvida de fronteira.
|
|
42
|
+
- Use [avaliações](references/evaluations.md) ao evoluir esta skill.
|
|
43
|
+
- Execute [scaffold.mjs](scripts/scaffold.mjs) para gerar os dois arquivos iniciais.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Contrato e binding
|
|
2
|
+
|
|
3
|
+
O contrato contém `name`, `kind`, documentação, schemas, metadata de UI, autorização
|
|
4
|
+
declarativa compartilhável e `mockHandler` de design quando aplicável.
|
|
5
|
+
|
|
6
|
+
O binding contém `handler`, `loads`, autorização dependente de dados carregados, execução
|
|
7
|
+
em background, eventos e idempotência. `bindAction(contract, binding)` produz o `ActionDef`
|
|
8
|
+
registrado no runtime.
|
|
9
|
+
|
|
10
|
+
Ordem cobrada pelo check: identidade → documentação → input/output → autorização →
|
|
11
|
+
handler/mock → comportamento. Campos específicos do kind permanecem junto do grupo ao qual
|
|
12
|
+
servem e o scaffold acompanha a forma atual do SDK.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const KINDS = new Set(['simple', 'form', 'list', 'view'])
|
|
4
|
+
const cap = (value) => value.charAt(0).toUpperCase() + value.slice(1)
|
|
5
|
+
const constant = (resource, verb) => resource + verb.split('-').map(cap).join('')
|
|
6
|
+
|
|
7
|
+
function contractBody({ resource, verb, kind }) {
|
|
8
|
+
const name = `${resource}.${verb}`
|
|
9
|
+
const identifier = constant(resource, verb)
|
|
10
|
+
const common = ` name: '${name}',\n kind: '${kind}',`
|
|
11
|
+
|
|
12
|
+
if (kind === 'list') {
|
|
13
|
+
return `export const ${identifier} = defineContract({
|
|
14
|
+
${common}
|
|
15
|
+
summary: 'Descreva o que esta consulta lista.',
|
|
16
|
+
description: 'Descreva o comportamento de negócio e seus limites.',
|
|
17
|
+
input: z.object({}),
|
|
18
|
+
output: z.object({ id: z.string() }),
|
|
19
|
+
paginate: 'cursor',
|
|
20
|
+
mockHandler: async () => ({ items: [], cursor: { next: null }, total: 0 }),
|
|
21
|
+
})`
|
|
22
|
+
}
|
|
23
|
+
if (kind === 'form') {
|
|
24
|
+
return `export const ${identifier} = defineContract({
|
|
25
|
+
${common}
|
|
26
|
+
label: 'Descreva a ação.',
|
|
27
|
+
description: 'Descreva o comportamento de negócio e seus limites.',
|
|
28
|
+
messages: { success: 'Operação concluída.', error: 'Não foi possível concluir.' },
|
|
29
|
+
input: z.object({ id: z.string() }),
|
|
30
|
+
output: z.object({ id: z.string() }),
|
|
31
|
+
fields: { id: { label: 'Identificador' } },
|
|
32
|
+
mockHandler: async (_ctx, input) => ({ id: input.id }),
|
|
33
|
+
})`
|
|
34
|
+
}
|
|
35
|
+
if (kind === 'view') {
|
|
36
|
+
return `export const ${identifier} = defineContract({
|
|
37
|
+
${common}
|
|
38
|
+
summary: 'Descreva o que esta consulta retorna.',
|
|
39
|
+
description: 'Descreva o comportamento de negócio e seus limites.',
|
|
40
|
+
input: z.object({ id: z.string() }),
|
|
41
|
+
output: z.object({ id: z.string() }),
|
|
42
|
+
mockHandler: async (_ctx, input) => ({ id: input.id }),
|
|
43
|
+
})`
|
|
44
|
+
}
|
|
45
|
+
return `export const ${identifier} = defineContract({
|
|
46
|
+
${common}
|
|
47
|
+
label: 'Descreva a ação.',
|
|
48
|
+
description: 'Descreva o comportamento de negócio e seus limites.',
|
|
49
|
+
messages: { success: 'Operação concluída.', error: 'Não foi possível concluir.' },
|
|
50
|
+
input: z.object({ id: z.string() }),
|
|
51
|
+
output: z.object({ ok: z.boolean() }),
|
|
52
|
+
mockHandler: async () => ({ ok: true }),
|
|
53
|
+
})`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function actionFiles({ resource, verb, kind = 'simple' }) {
|
|
57
|
+
if (!/^[a-z][a-z0-9-]*$/.test(resource) || !/^[a-z][a-z0-9-]*$/.test(verb)) {
|
|
58
|
+
throw new Error('resource e verb devem usar lowercase kebab-case')
|
|
59
|
+
}
|
|
60
|
+
if (!KINDS.has(kind)) throw new Error(`kind inválido: ${kind}`)
|
|
61
|
+
const identifier = constant(resource, verb)
|
|
62
|
+
return {
|
|
63
|
+
contract: `import { z } from 'zod'
|
|
64
|
+
import { defineContract } from '@softize/opus/core'
|
|
65
|
+
|
|
66
|
+
${contractBody({ resource, verb, kind })}
|
|
67
|
+
`,
|
|
68
|
+
binding: `import { bindAction } from '@softize/opus/core'
|
|
69
|
+
|
|
70
|
+
import { ${identifier} } from './${verb}.contract.js'
|
|
71
|
+
|
|
72
|
+
export const ${identifier}Impl = bindAction(${identifier}, {
|
|
73
|
+
handler: async (_ctx, _input) => {
|
|
74
|
+
throw new Error('Implemente o comportamento de ${resource}.${verb}.')
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
`,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function actionFileNames(verb) {
|
|
82
|
+
return { contract: `${verb}.contract.ts`, binding: `${verb}.ts` }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isMain = process.argv[1] !== undefined && import.meta.url === new URL(`file://${process.argv[1]}`).href
|
|
86
|
+
if (isMain) {
|
|
87
|
+
const [resource, verb, kind = 'simple'] = process.argv.slice(2)
|
|
88
|
+
if (resource === undefined || verb === undefined) {
|
|
89
|
+
console.error('uso: node scaffold.mjs <resource> <verb> [simple|form|list|view]')
|
|
90
|
+
process.exit(1)
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const generated = actionFiles({ resource, verb, kind })
|
|
94
|
+
const names = actionFileNames(verb)
|
|
95
|
+
process.stdout.write(`// ${names.contract}\n${generated.contract}\n// ${names.binding}\n${generated.binding}`)
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(error.message)
|
|
98
|
+
process.exit(1)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implement-opus-change
|
|
3
|
+
description: Implementa uma mudança em projeto baseado no Opus preservando contrato, binding, registro e projeções. Use ao alterar domínio, action, runtime ou integração que utilize @softize/opus.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Implementar mudança Opus
|
|
7
|
+
|
|
8
|
+
## Resultado
|
|
9
|
+
|
|
10
|
+
Entregar a mudança coerente com o protocolo do projeto, com contratos consumíveis sem
|
|
11
|
+
dependências server-only, bindings registrados, testes e gates verdes.
|
|
12
|
+
|
|
13
|
+
## Entradas
|
|
14
|
+
|
|
15
|
+
- critérios de aceite e instruções locais;
|
|
16
|
+
- `opus.json`, `opus.config.ts` e estrutura real do domínio afetado;
|
|
17
|
+
- versão instalada de `@softize/opus` e APIs que o repo já usa.
|
|
18
|
+
|
|
19
|
+
## Procedimento
|
|
20
|
+
|
|
21
|
+
1. Localizar contrato, binding, registro e consumidores da capacidade afetada.
|
|
22
|
+
2. Modelar primeiro o contrato observável: nome, descrição, input, output, erros e metadata.
|
|
23
|
+
3. Manter código compartilhável fora de banco, segredo, filesystem e drivers server-only.
|
|
24
|
+
4. Implementar o binding e registrar o `ActionDef` no domínio/runtime conforme a topologia local.
|
|
25
|
+
5. Usar `create-opus-action`, `test-opus-action` ou `build-opus-ui` quando a mudança entrar
|
|
26
|
+
nesses workflows especializados.
|
|
27
|
+
6. Atualizar manifest, docs geradas e exemplos somente pelos comandos do repo.
|
|
28
|
+
|
|
29
|
+
## Verificação
|
|
30
|
+
|
|
31
|
+
Rodar `opus check` no escopo correto, testes afetados, typecheck e os geradores que o
|
|
32
|
+
projeto declara. Conferir o diff gerado antes do handoff.
|
|
33
|
+
|
|
34
|
+
## Limites
|
|
35
|
+
|
|
36
|
+
- Opus é SDK/protocolo; não atribuir a ele DDD, arquitetura ou regra de negócio universal.
|
|
37
|
+
- Não criar fetch, tipo de transporte ou validação paralela quando o contrato já os fornece.
|
|
38
|
+
- Não supor caminhos fixos quando o repo adotou outra topologia válida.
|
|
39
|
+
|
|
40
|
+
## Recursos
|
|
41
|
+
|
|
42
|
+
- Leia [fronteiras do protocolo](references/protocol-boundaries.md) ao decidir onde o código mora.
|
|
43
|
+
- Use [avaliações](references/evaluations.md) ao evoluir esta skill.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Avaliações
|
|
2
|
+
|
|
3
|
+
- Dispara: “Implemente a mudança de cancelamento no domínio de pedidos que usa Opus.”
|
|
4
|
+
- Não dispara: “Atualize uma função utilitária sem relação com Opus.”
|
|
5
|
+
- Execução: alterar uma action existente, provar `opus check`, teste e projeção gerada sem diff inesperado.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Fronteiras do protocolo
|
|
2
|
+
|
|
3
|
+
- `defineContract`: identidade e superfície declarativa compartilhável.
|
|
4
|
+
- `bindAction`: execução server-only, loaders, autorização por linha e efeitos.
|
|
5
|
+
- domínio/runtime: composição e registro; não é fonte duplicada da documentação.
|
|
6
|
+
- `description`: explicação de negócio próxima da declaração e fonte das projeções.
|
|
7
|
+
- manifest/OpenAPI/docs: saídas geradas, nunca fonte editada em paralelo.
|
|
8
|
+
|
|
9
|
+
Use `defineAction` junto apenas quando o código não cruzar fronteira cliente/servidor e o
|
|
10
|
+
projeto já adotar explicitamente esse formato. Para uma action consumida pela UI, prefira
|
|
11
|
+
contrato separado do binding.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-opus-action
|
|
3
|
+
description: Testa actions Opus pela fronteira do contrato, cobrindo validação, autorização, handler e efeitos observáveis. Use ao criar ou alterar defineContract, bindAction, defineAction ou comportamento executado pelo runtime Opus.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Testar action Opus
|
|
7
|
+
|
|
8
|
+
## Resultado
|
|
9
|
+
|
|
10
|
+
Provar o comportamento público da action e suas regressões sem depender de transporte ou
|
|
11
|
+
infraestrutura real quando a unidade é o contrato.
|
|
12
|
+
|
|
13
|
+
## Procedimento
|
|
14
|
+
|
|
15
|
+
1. Importar o `ActionDef` produzido pelo binding; para contrato puro, testar `mockHandler`
|
|
16
|
+
apenas no modo design.
|
|
17
|
+
2. Executar com `runAction` e fornecer `user`, `can`, `loaded`, storage ou outros adapters
|
|
18
|
+
pelas opções do harness.
|
|
19
|
+
3. Cobrir sucesso, input inválido, autorização negada e erros de negócio materiais.
|
|
20
|
+
4. Afirmar output e efeitos observáveis como `emitted`, `logged` e estado do adapter fake.
|
|
21
|
+
5. Adicionar integração somente para a fronteira não provada pela unidade: banco, transporte,
|
|
22
|
+
registro real ou wiring entre actions.
|
|
23
|
+
|
|
24
|
+
## Verificação
|
|
25
|
+
|
|
26
|
+
Rodar o teste focado, a suíte do pacote e `opus check` no domínio. Um teste não substitui
|
|
27
|
+
typecheck nem freshness de manifest.
|
|
28
|
+
|
|
29
|
+
## Limites
|
|
30
|
+
|
|
31
|
+
- Não chamar handler diretamente quando isso pula validação/autorização do runtime.
|
|
32
|
+
- Não mockar o próprio contrato que deveria estar sob teste.
|
|
33
|
+
- Não usar snapshot amplo no lugar de invariantes observáveis.
|
|
34
|
+
|
|
35
|
+
## Recursos
|
|
36
|
+
|
|
37
|
+
- Leia [harness](references/harness.md) para escolher os fakes.
|
|
38
|
+
- Use [avaliações](references/evaluations.md) ao evoluir esta skill.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Harness Opus
|
|
2
|
+
|
|
3
|
+
- `runAction(action, input, options)`: aplica validação, autenticação, autorização, execução
|
|
4
|
+
e validação de output como o runtime.
|
|
5
|
+
- `testContext(options)`: cria `ActionContext` com `emit` e `log` observáveis.
|
|
6
|
+
- `memStorage()`: adapter de storage em memória com inspeção de chaves.
|
|
7
|
+
- `fake` e `fakeMany`: dados determinísticos derivados de schema.
|
|
8
|
+
|
|
9
|
+
Prefira o binding completo em testes de execução. Um contrato sem handler só executa via
|
|
10
|
+
`mockHandler` quando `OPUS_MODE=design`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrade-opus
|
|
3
|
+
description: Atualiza @softize/opus com leitura de changelog, materialização, migrações e gates reproduzíveis. Use ao alterar a versão do Opus ou corrigir drift após um upgrade do pacote.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Atualizar Opus
|
|
7
|
+
|
|
8
|
+
## Resultado
|
|
9
|
+
|
|
10
|
+
Resolver a nova versão no lockfile, aplicar suas migrações e deixar código, artefatos
|
|
11
|
+
materializados e projeções geradas consistentes no mesmo diff.
|
|
12
|
+
|
|
13
|
+
## Procedimento
|
|
14
|
+
|
|
15
|
+
1. Registrar versão atual e alvo e ler todas as entradas intermediárias do `CHANGELOG.md`,
|
|
16
|
+
priorizando seções Breaking e instruções de migração.
|
|
17
|
+
2. Atualizar a dependência com o package manager do repo.
|
|
18
|
+
3. Executar `opus setup` para materializar exatamente a versão resolvida.
|
|
19
|
+
4. Aplicar migrações no código por domínio, sem compatibilidade temporária silenciosa.
|
|
20
|
+
5. Rodar `opus check`, typecheck, testes, build e geradores declarados pelo projeto.
|
|
21
|
+
6. Conferir `base.json`, lockfile, skills, hooks, instruções e outputs gerados no diff.
|
|
22
|
+
|
|
23
|
+
## Verificação
|
|
24
|
+
|
|
25
|
+
Executar `opus check` novamente em checkout limpo com instalação frozen. Nenhum arquivo
|
|
26
|
+
gerenciado ou manifest pode mudar depois dos gates.
|
|
27
|
+
|
|
28
|
+
## Limites
|
|
29
|
+
|
|
30
|
+
- Não pular versões intermediárias do changelog.
|
|
31
|
+
- Não editar projeção gerenciada para contornar o setup; corrigir fonte ou colisão.
|
|
32
|
+
- Não publicar nem fazer deploy como consequência implícita do upgrade.
|
|
33
|
+
|
|
34
|
+
## Recursos
|
|
35
|
+
|
|
36
|
+
- Leia [checklist de upgrade](references/upgrade-checklist.md) antes do handoff.
|
|
37
|
+
- Use [avaliações](references/evaluations.md) ao evoluir esta skill.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Checklist de upgrade
|
|
2
|
+
|
|
3
|
+
- versão alvo presente em `package.json`, lockfile, `base.json` e `opus.json` aplicáveis;
|
|
4
|
+
- `opus setup` e `opus check` verdes;
|
|
5
|
+
- breakings intermediários tratados;
|
|
6
|
+
- typecheck, testes e build verdes;
|
|
7
|
+
- manifest, OpenAPI e docs regenerados quando configurados;
|
|
8
|
+
- diff sem edição manual de arquivo gerenciado e sem compatibilidade morta.
|
|
@@ -2,7 +2,7 @@ import { defineDomain } from '@softize/opus/core'
|
|
|
2
2
|
import { taskListImpl } from './actions/list.ts'
|
|
3
3
|
|
|
4
4
|
/** Domínio-exemplo do esqueleto. Troque pelo seu domínio real — a skill
|
|
5
|
-
* `create-action` gera actions novas no formato canônico. O domínio registra
|
|
5
|
+
* `create-opus-action` gera actions novas no formato canônico. O domínio registra
|
|
6
6
|
* o BOUND (contrato + handler); o contrato puro é o que a web consome. */
|
|
7
7
|
export const tasksDomain = defineDomain({
|
|
8
8
|
name: 'task',
|
|
@@ -310,10 +310,19 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
310
310
|
<Popover
|
|
311
311
|
open={open}
|
|
312
312
|
onOpenChange={(v) => {
|
|
313
|
-
// Fechar devolve o foco pro gatilho, e o gatilho abre AO RECEBER FOCO
|
|
314
|
-
// guarda
|
|
315
|
-
//
|
|
316
|
-
|
|
313
|
+
// Fechar devolve o foco pro gatilho, e o gatilho abre AO RECEBER FOCO — sem
|
|
314
|
+
// guarda, o seletor reabre sozinho ("abre, fecha e abre de novo").
|
|
315
|
+
//
|
|
316
|
+
// A guarda vale UM FRAME, não até ser consumida. Fechar clicando noutro lugar da
|
|
317
|
+
// página não devolve o foco pra cá, então uma trava que espera esse retorno ficaria
|
|
318
|
+
// armada pra sempre e engoliria a PRÓXIMA abertura por teclado — trocando um
|
|
319
|
+
// defeito visível por um que só aparece pra quem navega por Tab.
|
|
320
|
+
if (!v) {
|
|
321
|
+
closingRef.current = true
|
|
322
|
+
requestAnimationFrame(() => {
|
|
323
|
+
closingRef.current = false
|
|
324
|
+
})
|
|
325
|
+
}
|
|
317
326
|
setOpen(v)
|
|
318
327
|
}}
|
|
319
328
|
>
|
|
@@ -370,9 +379,12 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
|
|
|
370
379
|
o texto rolava pro fim ao ganhar foco (o browser leva o cursor pro final)
|
|
371
380
|
e o começo do título sumia — num campo estreito, a pessoa via o meio de
|
|
372
381
|
uma frase. `<span>` trunca com reticências, que é o certo. */}
|
|
373
|
-
{
|
|
382
|
+
{/* No MÚLTIPLO os chips ocupam o lugar do rótulo — mas quando não há nenhum,
|
|
383
|
+
o gatilho ficaria em branco (o input que carregava o placeholder virou
|
|
384
|
+
invisível). Campo mudo é pior que campo vazio: não diz nem o que é. */}
|
|
385
|
+
{(!props.multiple || selectedValues.length === 0) && (
|
|
374
386
|
<span data-slot="select-trigger-label" className={cn('min-w-0 flex-1 truncate text-left text-sm', selectedOption === undefined && 'text-muted-foreground')}>
|
|
375
|
-
{selectedOption?.triggerLabel ??
|
|
387
|
+
{props.multiple ? placeholder : (selectedOption?.triggerLabel ?? selectedOption?.label ?? placeholder)}
|
|
376
388
|
</span>
|
|
377
389
|
)}
|
|
378
390
|
<input
|
|
@@ -52,7 +52,7 @@ export const workspaceCreate = bindAction(workspaceCreateContract, {
|
|
|
52
52
|
## Ordem canônica dos campos
|
|
53
53
|
|
|
54
54
|
> O `opus check` reprova fora desta ordem — é a régua, 0 violação antes de entregar. A skill
|
|
55
|
-
> `create-action` gera
|
|
55
|
+
> `create-opus-action` gera contrato e binding no formato certo por construção.
|
|
56
56
|
|
|
57
57
|
```text
|
|
58
58
|
name → kind → (label / summary / messages / tags…)
|
|
@@ -50,9 +50,9 @@ só os arquivos do app; o que a raiz precisa ter vira aviso, sem clobber).
|
|
|
50
50
|
|
|
51
51
|
## MCP — estado vivo pros agentes
|
|
52
52
|
|
|
53
|
-
> O server MCP expõe
|
|
53
|
+
> O server MCP expõe introspecção, check e scaffold de action. É como um agente lê a estrutura e cria
|
|
54
54
|
> action no formato canônico sem decorar convenção.
|
|
55
55
|
|
|
56
56
|
`opus mcp` sobe o server (configurado em `.mcp.json`): o agente consulta a estrutura, roda o
|
|
57
|
-
check e gera o esqueleto de uma action nova pela skill `create-action` — a mesma régua do
|
|
57
|
+
check e gera o esqueleto de uma action nova pela skill `create-opus-action` — a mesma régua do
|
|
58
58
|
`opus check`, por construção.
|
|
@@ -107,4 +107,4 @@ const [open, setOpen] = useState(false)
|
|
|
107
107
|
|
|
108
108
|
Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
|
|
109
109
|
contrato), o movimento não é dialeto local: é evoluir o componente **no Opus**, com a divergência
|
|
110
|
-
declarada (skill `opus`) — assim a mudança vale pra casa toda, e esta doc passa a mostrá-la.
|
|
110
|
+
declarada (skill `build-opus-ui`) — assim a mudança vale pra casa toda, e esta doc passa a mostrá-la.
|
|
@@ -32,7 +32,7 @@ export const SkillEntity = defineEntity({
|
|
|
32
32
|
|
|
33
33
|
## Código camelCase, banco snake
|
|
34
34
|
|
|
35
|
-
> Regra dura
|
|
35
|
+
> Regra dura do protocolo: uma camada em snake e outra em camel é defeito — não tem meio-termo.
|
|
36
36
|
|
|
37
37
|
O schema tipado do Kysely e **toda** query (select/insert/update/where) usam camelCase
|
|
38
38
|
(`workspaceId`, `ghRepo`). O banco é snake (a DDL no schema idempotente — `db/schema.sql`).
|
|
@@ -6,7 +6,7 @@ title: Microcopy
|
|
|
6
6
|
|
|
7
7
|
Código em inglês; o que humano lê (UI, comentário, doc) em pt-BR. Frases começam com
|
|
8
8
|
maiúscula e terminam com ponto; labels curtos em sentence case, sem ponto. **Esta página
|
|
9
|
-
é a fonte** —
|
|
9
|
+
é a fonte** — `build-opus-ui` aplica esta convenção (regra duplicada em vez de apontada é
|
|
10
10
|
defeito).
|
|
11
11
|
|
|
12
12
|
## Língua por camada
|
|
@@ -33,7 +33,7 @@ render(
|
|
|
33
33
|
Pra rolar na horizontal, acrescente <ScrollBar orientation="horizontal" /> como filho e deixe o conteúdo numa linha que não quebra (flex + w-max).
|
|
34
34
|
|
|
35
35
|
```tsx preview col
|
|
36
|
-
const skills = ['
|
|
36
|
+
const skills = ['implement-opus-change', 'create-opus-action', 'test-opus-action', 'build-opus-ui', 'upgrade-opus']
|
|
37
37
|
|
|
38
38
|
render(
|
|
39
39
|
<ScrollArea className="w-full rounded-md border border-border whitespace-nowrap">
|
|
@@ -142,7 +142,7 @@ render(
|
|
|
142
142
|
## Divergências declaradas vs shadcn
|
|
143
143
|
|
|
144
144
|
> A regra anti-drift: o que não está declarado aqui (e no `theme.css`) é drift e deve ser
|
|
145
|
-
> sincronizado — skill `opus`.
|
|
145
|
+
> sincronizado — skill `build-opus-ui`.
|
|
146
146
|
|
|
147
147
|
1. Formato HSL nos tokens (o upstream migrou pra oklch) — legibilidade e ferramentas nossas; os
|
|
148
148
|
valores acompanham o upstream, só o formato difere.
|
|
@@ -35,6 +35,6 @@ import { Button, Dialog, useAction } from '@softize/opus/ui/react'
|
|
|
35
35
|
|
|
36
36
|
- **Origem shadcn** — port curado do shadcn (Radix + cmdk). Toda divergência do upstream é
|
|
37
37
|
DECLARADA no componente ou no tema — o que diverge sem declaração é drift e deve ser
|
|
38
|
-
sincronizado (skill `opus`).
|
|
38
|
+
sincronizado (skill `build-opus-ui`).
|
|
39
39
|
- **Nativo do Opus** — nasceu aqui (os patterns de action). Não tem upstream pra acompanhar; a
|
|
40
40
|
referência é esta doc.
|
|
@@ -124,6 +124,8 @@ export const docSessionDelete = defineContract({
|
|
|
124
124
|
kind: 'simple',
|
|
125
125
|
label: 'Excluir sessão',
|
|
126
126
|
messages: { success: 'Sessão excluída', error: 'Falha ao excluir' },
|
|
127
|
+
input: z.object({ id: z.string() }),
|
|
128
|
+
output: z.object({ ok: z.boolean() }),
|
|
127
129
|
// ConfirmSpec DECLARADO no contrato: o ActionTrigger monta o dialog sozinho.
|
|
128
130
|
confirm: {
|
|
129
131
|
title: 'Excluir a sessão?',
|
|
@@ -131,8 +133,6 @@ export const docSessionDelete = defineContract({
|
|
|
131
133
|
confirmLabel: 'Excluir',
|
|
132
134
|
destructive: true,
|
|
133
135
|
},
|
|
134
|
-
input: z.object({ id: z.string() }),
|
|
135
|
-
output: z.object({ ok: z.boolean() }),
|
|
136
136
|
})
|
|
137
137
|
|
|
138
138
|
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
|
package/src/ui/theme.css
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* + um `@source` pros componentes do Opus (monorepo), e sobrescreve vars pra identidade.
|
|
6
6
|
*
|
|
7
7
|
* ── DIVERGÊNCIAS DECLARADAS vs shadcn upstream (regra: o que não está aqui é DRIFT
|
|
8
|
-
* e deve ser sincronizado; ver skill `
|
|
8
|
+
* e deve ser sincronizado; ver skill `build-opus-ui`) ─────────────────────────────
|
|
9
9
|
* 1. Formato HSL nos tokens (upstream migrou pra oklch) — legibilidade/ferramentas
|
|
10
10
|
* nossas; os VALORES acompanham o upstream, só o formato difere.
|
|
11
11
|
* 2. Elevação dark: --card/--popover ACIMA de --background (10% vs 3.9%) — identidade
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"SessionStart": [
|
|
4
|
-
{
|
|
5
|
-
"hooks": [
|
|
6
|
-
{
|
|
7
|
-
"type": "command",
|
|
8
|
-
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/link-memory-on-start.mjs\"",
|
|
9
|
-
"timeout": 10
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
],
|
|
14
|
-
"Stop": [
|
|
15
|
-
{
|
|
16
|
-
"hooks": [
|
|
17
|
-
{
|
|
18
|
-
"type": "command",
|
|
19
|
-
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/opus-check-on-stop.mjs\"",
|
|
20
|
-
"timeout": 120
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook SessionStart da base Opus: liga a memória versionada do repo
|
|
3
|
-
* (`.claude/memory`, política da skill `memory`) ao diretório que o Claude Code
|
|
4
|
-
* lê (`~/.claude/projects/<hash>/memory`). Sem isso, sessão em worktree/máquina
|
|
5
|
-
* nova roda AMNÉSICA — recall vazio e memória gravada num diretório órfão que
|
|
6
|
-
* morre com o worktree. É o `link-memory.sh` automatizado: idempotente, com
|
|
7
|
-
* backup de memória local pré-existente, e fail-open (repo sem `.claude/memory`
|
|
8
|
-
* versionado → no-op; qualquer erro → segue sem travar a sessão).
|
|
9
|
-
*/
|
|
10
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, renameSync, rmdirSync, symlinkSync, unlinkSync } from 'node:fs'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
|
-
import { dirname, join } from 'node:path'
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const root = process.env.CLAUDE_PROJECT_DIR ?? process.cwd()
|
|
16
|
-
const memRepo = join(root, '.claude', 'memory')
|
|
17
|
-
// Sem memória versionada → o repo não adota a política; nada a ligar.
|
|
18
|
-
if (!existsSync(memRepo)) process.exit(0)
|
|
19
|
-
|
|
20
|
-
// O hash do projeto é o caminho absoluto com separadores (e pontos) virando '-',
|
|
21
|
-
// como o Claude Code nomeia ~/.claude/projects/. Espelha o link-memory.sh.
|
|
22
|
-
const hash = root.replace(/[/.]/g, '-')
|
|
23
|
-
const dest = join(homedir(), '.claude', 'projects', hash, 'memory')
|
|
24
|
-
|
|
25
|
-
let st = null
|
|
26
|
-
try {
|
|
27
|
-
st = lstatSync(dest)
|
|
28
|
-
} catch {
|
|
29
|
-
/* Não existe → segue pro link. */
|
|
30
|
-
}
|
|
31
|
-
if (st?.isSymbolicLink()) {
|
|
32
|
-
if (readlinkSync(dest) === memRepo) process.exit(0) // Já ligado.
|
|
33
|
-
unlinkSync(dest) // Aponta pra outro lugar → religa.
|
|
34
|
-
} else if (st?.isDirectory()) {
|
|
35
|
-
// Memória local pré-existente: com conteúdo → backup (nunca descarta); vazia → sai.
|
|
36
|
-
if (readdirSync(dest).length > 0) renameSync(dest, `${dest}.local-${Date.now()}`)
|
|
37
|
-
else rmdirSync(dest)
|
|
38
|
-
} else if (st !== null) {
|
|
39
|
-
unlinkSync(dest) // Arquivo solto no lugar do diretório — afasta.
|
|
40
|
-
}
|
|
41
|
-
mkdirSync(dirname(dest), { recursive: true })
|
|
42
|
-
symlinkSync(memRepo, dest)
|
|
43
|
-
} catch {
|
|
44
|
-
/* Fail-open: memória desligada é degradação, não motivo pra travar a sessão. */
|
|
45
|
-
}
|
|
46
|
-
process.exit(0)
|