@softize/opus 13.0.0 → 14.0.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 +81 -0
- package/PROMOTED.md +46 -0
- package/README.md +28 -19
- package/bin/cli.mjs +87 -216
- package/bin/lib/cli-shared.mjs +131 -0
- package/bin/lib/copy.mjs +276 -6
- package/bin/lib/db.mjs +16 -74
- package/bin/lib/gen-openapi.mjs +3 -3
- package/bin/lib/gen-runner.mjs +1 -1
- package/bin/lib/gen.mjs +14 -69
- package/bin/lib/mcp.mjs +3 -1
- package/bin/lib/seed.mjs +5 -62
- package/docs/code-style.md +4 -1
- package/docs/ownership-vs-shadcn-lock.md +2 -3
- package/docs/protocol.md +7 -7
- package/docs/releasing.md +8 -2
- package/package.json +7 -3
- package/registry/instructions/opus.md +3 -3
- package/registry/templates/app/package.json +1 -1
- package/registry/templates/app/src/App.tsx +11 -6
- package/registry/templates/app/src/main.tsx +4 -4
- package/src/audit/drivers/console.ts +1 -0
- package/src/auth/drivers/better-auth.ts +1 -0
- package/src/auth/drivers/jwt.ts +1 -0
- package/src/cache/drivers/memory.ts +1 -0
- package/src/client/drivers/fetch.ts +2 -1
- package/src/core/actions.ts +6 -1
- package/src/core/audit.ts +9 -3
- package/src/core/contracts.ts +7 -0
- package/src/core/domain.ts +1 -1
- package/src/core/errors.ts +18 -15
- package/src/core/index.ts +4 -2
- package/src/core/package-version.ts +26 -0
- package/src/core/reactions.ts +1 -1
- package/src/core/runtime.ts +33 -23
- package/src/core/schedules.ts +1 -1
- package/src/core/types.ts +5 -6
- package/src/dsl/eval.ts +2 -2
- package/src/dsl/kysely.ts +2 -2
- package/src/dsl/loads.ts +1 -1
- package/src/dsl/parser.ts +5 -5
- package/src/events/drivers/mitt.ts +1 -0
- package/src/mcp/index.ts +2 -1
- package/src/observability/drivers/opentelemetry.ts +1 -0
- package/src/queue/drivers/bullmq.ts +3 -3
- package/src/scheduler/drivers/node-cron.ts +3 -2
- package/src/scheduler/every.ts +7 -7
- package/src/schema/openapi.ts +3 -3
- package/src/seed/index.ts +29 -0
- package/src/server/drivers/fastify.ts +5 -2
- package/src/server/drivers/node.ts +9 -6
- package/src/server/index.ts +3 -1
- package/src/storage/drivers/fs.ts +1 -0
- package/src/testing/index.ts +3 -3
- package/src/ui/components/patterns/action-list-dialog.tsx +10 -3
- package/src/ui/components/patterns/confirm.tsx +2 -31
- package/src/ui/components/patterns/content-header.tsx +44 -137
- package/src/ui/components/patterns/data-state.tsx +42 -68
- package/src/ui/components/patterns/dock.tsx +20 -3
- package/src/ui/components/patterns/form.tsx +26 -22
- package/src/ui/components/patterns/list.tsx +18 -12
- package/src/ui/components/patterns/page-state.tsx +39 -51
- package/src/ui/components/patterns/page.tsx +37 -54
- package/src/ui/components/patterns/sidebar.tsx +17 -6
- package/src/ui/components/patterns/state-surface.tsx +148 -0
- package/src/ui/components/patterns/surface-header.tsx +119 -0
- package/src/ui/components/patterns/trigger.tsx +21 -25
- package/src/ui/components/patterns/view.tsx +29 -22
- package/src/ui/components/primitives/alert.tsx +1 -27
- package/src/ui/components/primitives/ask.tsx +3 -3
- package/src/ui/components/primitives/avatar.tsx +15 -5
- package/src/ui/components/primitives/badge.tsx +5 -41
- package/src/ui/components/primitives/breadcrumb.tsx +2 -2
- package/src/ui/components/primitives/button.tsx +39 -30
- package/src/ui/components/primitives/calendar.tsx +28 -2
- package/src/ui/components/primitives/carousel.tsx +3 -3
- package/src/ui/components/primitives/chat.tsx +1 -1
- package/src/ui/components/primitives/checkbox.tsx +1 -1
- package/src/ui/components/primitives/command.tsx +2 -2
- package/src/ui/components/primitives/control.ts +72 -0
- package/src/ui/components/primitives/copyable.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +12 -7
- package/src/ui/components/primitives/dot.tsx +1 -25
- package/src/ui/components/primitives/drawer.tsx +10 -3
- package/src/ui/components/primitives/field.tsx +3 -3
- package/src/ui/components/primitives/icon-picker.tsx +3 -1
- package/src/ui/components/primitives/input-group.tsx +12 -9
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/item.tsx +3 -1
- package/src/ui/components/primitives/menu.tsx +1 -7
- package/src/ui/components/primitives/pagination.tsx +16 -8
- package/src/ui/components/primitives/progress.tsx +32 -3
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/resizable.tsx +3 -1
- package/src/ui/components/primitives/select.tsx +6 -6
- package/src/ui/components/primitives/slider.tsx +5 -1
- package/src/ui/components/primitives/sonner.tsx +3 -0
- package/src/ui/components/primitives/spinner.tsx +13 -16
- package/src/ui/components/primitives/switch.tsx +5 -1
- package/src/ui/components/primitives/tabs.tsx +6 -3
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle.tsx +9 -4
- package/src/ui/components/primitives/tooltip.tsx +1 -0
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/action-form.md +36 -3
- package/src/ui/docs/content/action-list-dialog.md +2 -2
- package/src/ui/docs/content/action-list.md +13 -2
- package/src/ui/docs/content/action-trigger.md +12 -5
- package/src/ui/docs/content/action-view.md +11 -3
- package/src/ui/docs/content/ask.md +11 -0
- package/src/ui/docs/content/avatar.md +7 -3
- package/src/ui/docs/content/button.md +30 -14
- package/src/ui/docs/content/calendar.md +13 -0
- package/src/ui/docs/content/card.md +26 -0
- package/src/ui/docs/content/chat.md +20 -0
- package/src/ui/docs/content/cli.md +71 -19
- package/src/ui/docs/content/communication.md +36 -0
- package/src/ui/docs/content/composer.md +15 -0
- package/src/ui/docs/content/content.md +17 -1
- package/src/ui/docs/content/copyable.md +8 -0
- package/src/ui/docs/content/data-state.md +17 -13
- package/src/ui/docs/content/detail.md +19 -1
- package/src/ui/docs/content/dialog.md +1 -4
- package/src/ui/docs/content/dictionary-value.md +9 -2
- package/src/ui/docs/content/dock.md +8 -0
- package/src/ui/docs/content/dot.md +8 -0
- package/src/ui/docs/content/empty.md +2 -2
- package/src/ui/docs/content/getting-started.md +2 -2
- package/src/ui/docs/content/icon-picker.md +11 -0
- package/src/ui/docs/content/input.md +1 -1
- package/src/ui/docs/content/item.md +1 -1
- package/src/ui/docs/content/label.md +7 -0
- package/src/ui/docs/content/menu.md +6 -0
- package/src/ui/docs/content/metric-card.md +13 -0
- package/src/ui/docs/content/page.md +20 -4
- package/src/ui/docs/content/pagination.md +11 -9
- package/src/ui/docs/content/popover.md +6 -0
- package/src/ui/docs/content/progress.md +8 -11
- package/src/ui/docs/content/select.md +5 -5
- package/src/ui/docs/content/semantic-context.md +5 -4
- package/src/ui/docs/content/sidebar.md +13 -47
- package/src/ui/docs/content/skeleton.md +6 -0
- package/src/ui/docs/content/spinner.md +9 -6
- package/src/ui/docs/content/split.md +21 -0
- package/src/ui/docs/content/switch.md +1 -1
- package/src/ui/docs/content/tabs.md +1 -1
- package/src/ui/docs/content/textarea.md +7 -0
- package/src/ui/docs/content/toggle.md +1 -1
- package/src/ui/docs/content/tokens.md +4 -4
- package/src/ui/docs/content/truncate.md +8 -0
- package/src/ui/docs/content/ui.md +14 -0
- package/src/ui/docs/doc-client.tsx +5 -5
- package/src/ui/docs/doc.tsx +26 -14
- package/src/ui/docs/registry.tsx +5 -5
- package/src/ui/docs/standalone.tsx +2 -2
- package/src/ui/drivers/react.tsx +12 -12
- package/src/ui/lib/action-errors.ts +45 -0
- package/src/ui/lib/zod-pt-br.ts +31 -4
- package/src/ui/meta.ts +8 -8
- package/src/ui/react.tsx +10 -16
- package/src/ui/theme.css +10 -8
- package/src/vite/design.ts +6 -18
- package/src/ui/components/patterns/shell-nav.tsx +0 -147
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,87 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 14.0.0 — 2026-09-09
|
|
11
|
+
|
|
12
|
+
Estados vazio, carregando e erro passam a uma composição só. `DataState` compõe `Empty` (moldura
|
|
13
|
+
sólida, `bare` dentro de tabela) e o mesmo `Alert` de contexto `danger` com botão de recuperação
|
|
14
|
+
que `PageState` já usava; o `Spinner` vira decorativo e o contêiner carrega o único `role="status"`.
|
|
15
|
+
As props de estado têm um nome só em `DataState`, `PageState`, `ActionList` e `ActionListDialog`:
|
|
16
|
+
`emptyMessage`, `errorMessage`, `retryLabel`; `PageState` ganha `onRetry` e `ActionView` ganha
|
|
17
|
+
`emptyMessage` como atalho. `ContentHeader` e `PageHeader` renderizam a mesma anatomia
|
|
18
|
+
(`surface-header.tsx`), e `Content` passa a usar `count`, como `Page`.
|
|
19
|
+
|
|
20
|
+
A escala de `size` é uma só (`primitives/control.ts`): `xs`, `sm`, `default`, `lg` (1.5, 2, 2.25
|
|
21
|
+
e 2.5rem) e `icon-xs`, `icon-sm`, `icon`, `icon-lg` (1.5, 1.75, 2.25 e 2.5rem), consumida por
|
|
22
|
+
Button, InputGroupButton, Toggle, Select, Tabs, Switch, Item, Avatar, Spinner, Pagination e
|
|
23
|
+
ActionTrigger. O degrau de 1.75rem que os consumidores forçavam por classe agora é `icon-sm`.
|
|
24
|
+
`Button.icon` recebe um nó (`icon={<Plus />}`), como todos os outros componentes. `LabelHelp` é
|
|
25
|
+
exportado pelo barrel e documentado em `form.md`.
|
|
26
|
+
|
|
27
|
+
O extrator de copy alcança o que antes escapava do inventário: chamadas imperativas de diálogo
|
|
28
|
+
(`dialog.confirm`, `alert`, `prompt`, `choose` — título, descrição e rótulos das ações),
|
|
29
|
+
`TooltipContent`, rótulos em arrays literais locais percorridos por `map`, o `help` de
|
|
30
|
+
`LabelHelp` e elementos JSX com genérico (`<ActionList<A, B>>`). Os campos de estado seguem os
|
|
31
|
+
nomes novos (`emptyMessage`, `errorMessage`, `retryLabel`).
|
|
32
|
+
|
|
33
|
+
**Breaking:** renomeie `emptyText`, `errorText` e `retryText` para `emptyMessage`,
|
|
34
|
+
`errorMessage` e `retryLabel` em `DataState`, `PageState` e `ActionListDialog`
|
|
35
|
+
(`Select.emptyText` não muda). Troque `Content.meta` por `count`; `ContentHeader` só existe
|
|
36
|
+
dentro de `Content`. `icon-sm` passa a medir 1.75rem e `Avatar` segue a escala (`sm` antigo é
|
|
37
|
+
`xs`; `default` cresce para 2.25rem); classes `size-7` forçadas viram `size="icon-sm"`.
|
|
38
|
+
`Button.icon` deixa de aceitar componente: `icon={Trash}` vira `icon={<Trash />}`. Removidos:
|
|
39
|
+
`TbdlibProvider` (use `OpusProvider`), `ShellNav*` (use `SidebarNav`), `confirm()` e `ConfirmHost`
|
|
40
|
+
(use `dialog.confirm` e `DialogHost`), `FieldSpec.hint` (use `help`) e as variantes semânticas
|
|
41
|
+
legadas de Button, Badge, Alert, Dot e MenuItem — declare `context` e `variant`. Testes que
|
|
42
|
+
liam o markup do vazio de `DataState` passam a olhar `[data-slot=empty]`.
|
|
43
|
+
|
|
44
|
+
## 13.1.0 — 2026-09-08
|
|
45
|
+
|
|
46
|
+
Erros deixam de falar inglês e de vazar detalhe interno. `normalizeError` devolve a microcopy fixa
|
|
47
|
+
"Não foi possível concluir a operação. Tente novamente." em `internal.unhandled`; o texto da
|
|
48
|
+
exceção original vai para o logger do runtime e para `cause`, e o servidor não serializa `cause` de
|
|
49
|
+
erro interno na resposta. Todas as mensagens de `ActionError` do runtime, do harness de `testing`,
|
|
50
|
+
dos drivers `node` e `fetch`, do `AuditEmitter`, do scheduler, da DSL e do modo design passam a
|
|
51
|
+
pt-BR com os códigos inalterados; o OpenAPI descreve as respostas como "Sucesso", "Erro do cliente"
|
|
52
|
+
e "Erro do servidor". Na UI, `humanizeActionError` (`lib/action-errors.ts`) concentra a allowlist
|
|
53
|
+
de erros de negócio que `ActionTrigger` já usava e passa a valer em `ActionForm` e `ActionView`:
|
|
54
|
+
o código técnico não aparece mais como título, e as mensagens Zod em pt-BR cobrem todos os códigos
|
|
55
|
+
do zod v3.
|
|
56
|
+
|
|
57
|
+
Button sem `context` resolve `neutral` para `outline`, `ghost` e `link` (e `primary` para `solid` e
|
|
58
|
+
`subtle`), o que devolve ao `ghost` a cor de texto que ele tinha antes da migração semântica.
|
|
59
|
+
`Calendar` nasce em pt-BR (locale, dropdown de mês e rótulos de navegação). `Progress` e
|
|
60
|
+
`SurfaceStatus` aceitam `context`; `ActionView.loading` aceita boolean. O anel de foco é um só
|
|
61
|
+
(`focusRing`), os tokens `*-destructive` deram lugar à família `context-danger` nos controles de
|
|
62
|
+
formulário, Dialog e Drawer compartilham o overlay e o thumb do Slider usa `bg-background`.
|
|
63
|
+
Textos em inglês de Command, Drawer, Breadcrumb, Carousel, do site e dos exemplos foram
|
|
64
|
+
traduzidos; o site deixa de sobrescrever o fundo do tema. Breadcrumb, carousel, progress,
|
|
65
|
+
resizable e slider passam a ejetados no `registry.lock.json`.
|
|
66
|
+
|
|
67
|
+
Descontinuados, com aviso no editor e sem mudança de comportamento: as variantes legadas
|
|
68
|
+
(`default`, `secondary`, `destructive`, `success`, `warning`, `info`), `confirm()`, `ConfirmHost`,
|
|
69
|
+
`FieldSpec.hint` (use `help`), `ShellNav*` (use `SidebarNav`) e `TbdlibProvider`, renomeado para
|
|
70
|
+
`OpusProvider`. `'danger'` deixa de ser aceito como *variante* legada de Badge e Alert no tipo (era
|
|
71
|
+
um contexto disfarçado): declare `context="danger"`. O botão de confirmar do `ActionTrigger` passa a
|
|
72
|
+
ser sempre `solid` — `danger` quando a action é `destructive`, senão o `context` do gatilho — e não
|
|
73
|
+
herda mais o `variant` do gatilho; a permissão negada (`authorization`/`authentication`) entra na
|
|
74
|
+
allowlist de erros legíveis e o `ActionView` não oferece "Tentar de novo" nesses casos. `openapiInfo` default passa a `{ title: 'Opus API', version }` com a versão do
|
|
75
|
+
pacote, e o servidor MCP reporta a versão real; a marca `tbdlib` saiu do código.
|
|
76
|
+
|
|
77
|
+
O CLI perde os comandos `add` e `list`, mortos desde que os componentes viraram biblioteca, e
|
|
78
|
+
ganha um `--help` fiel ao dispatch: subcomandos de `db` (`check`, `migrate`, `scaffold`), flags
|
|
79
|
+
`--json` e `--monorepo`, `check --help` com as nove regras e `pre-push --help`. `gen`, `db` e
|
|
80
|
+
`seed` compartilham `bin/lib/cli-shared.mjs`, e `opus.config.ts` ausente produz a mesma mensagem
|
|
81
|
+
nos três. A documentação de `cli.md` cobre `opus copy`, o hook de pré-push e as flags; quinze
|
|
82
|
+
páginas de componentes ganham tabela de propriedades e os hooks `useListAction`,
|
|
83
|
+
`useFormAction`, `useViewAction`, `useTriggerAction` e `useDicts` aparecem nas páginas que os
|
|
84
|
+
usam. `PROMOTED.md` viaja no pacote.
|
|
85
|
+
|
|
86
|
+
**Atenção:** a peer `react`/`react-dom` passa a `^19.0.0`. O suporte a React 18 nunca funcionou
|
|
87
|
+
de fato, porque os componentes dependem de `ref` como prop; consumidores em 19 não precisam
|
|
88
|
+
fazer nada. O default de `ghost` sem `context` muda de cor: quem quiser o azul de antes declara
|
|
89
|
+
`context="primary"`.
|
|
90
|
+
|
|
10
91
|
## 13.0.0 — 2026-09-07
|
|
11
92
|
|
|
12
93
|
Dialogs passam a formar uma única família. `Dialog mode="alert"` oferece a semântica de
|
package/PROMOTED.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Enhancements promovidas ao Opus
|
|
2
|
+
|
|
3
|
+
O registro CURADO da régua "o Opus cresce por reincidência". O fluxo vivo NÃO passa por
|
|
4
|
+
edição manual deste arquivo: o agente no projeto aponta em `.opus/issues.jsonl` do repo
|
|
5
|
+
dele (instrução na skill `implement-opus-change`; `type` **enhancement** ou **bug**) →
|
|
6
|
+
o Maestro colhe no boot da sessão e abre a **Issue no repo do Opus**
|
|
7
|
+
(`github.com/softize-dev/opus`, dedup por hash + ledger), onde a triagem e a decisão
|
|
8
|
+
acontecem (aberta = pendente, fechada = decidida). Enhancement aceita com **n ≥ 2**
|
|
9
|
+
ocorrências reais — comportamento se paga na reincidência; estilo e conveniência não.
|
|
10
|
+
Bug (defeito NO Opus) aciona com n=1: vira fix + CHANGELOG, **não** entra neste ledger.
|
|
11
|
+
|
|
12
|
+
Aqui fica só o DECIDIDO e a espera triada: entrada nova/movida entra **no PR que
|
|
13
|
+
implementa a promoção** (git write só pega carona em trabalho que já é git — nenhum
|
|
14
|
+
serviço escreve em repo). Rejeitadas ficam fechadas no GitHub com o motivo; rejeição que
|
|
15
|
+
vira REGRA vai pra skill, não pra cá.
|
|
16
|
+
|
|
17
|
+
## Em espera
|
|
18
|
+
|
|
19
|
+
### Truncate (UI)
|
|
20
|
+
Texto truncado com tooltip SÓ quando transborda (medição via ResizeObserver).
|
|
21
|
+
- n = 2 — admin (listas com célula estreita) e maestro (`Ai.tsx`: `truncate` + `title`
|
|
22
|
+
sempre presente, hooks e MCP). Apontado como issue #2 em `softize-dev/opus`. Jul/2026.
|
|
23
|
+
|
|
24
|
+
### Driver ai via CLI `claude` (SDK)
|
|
25
|
+
`complete()` one-shot spawnando o CLI headless com a credencial da ASSINATURA do host
|
|
26
|
+
(OAuth), em vez de API key — quando o consumidor já vive numa máquina com Claude Code
|
|
27
|
+
e não quer cobrança por token à parte. Viraria um `@softize/opus/ai/claude-cli` ao
|
|
28
|
+
lado do `ai/anthropic` (mesmo contrato `AiAdapter`, outra credencial/custo).
|
|
29
|
+
- n = 1 — maestro (`src/naming.ts`: título de sessão por IA; escolha DELIBERADA pelo
|
|
30
|
+
CLI, comentada no arquivo). Jul/2026.
|
|
31
|
+
|
|
32
|
+
## Promovidas
|
|
33
|
+
|
|
34
|
+
### Copyable (UI) — 2.15.0
|
|
35
|
+
Clicar-pra-copiar com feedback (ícone check ~1.5s), pra IDs/tokens/slugs/URLs. Botão-ícone
|
|
36
|
+
sem filhos; rótulo + ícone com filhos. Exportado de `@softize/opus/ui/react`.
|
|
37
|
+
Apontado no exercício e2e do fluxo GitHub Issues (jul/2026): o agente precisou de "copiar
|
|
38
|
+
link" na toolbar do preview do Maestro, compôs um `CopyButton` local (workaround), filou o
|
|
39
|
+
enhancement → issue #4 em `softize-dev/opus`, aceita na triagem. O workaround local sai no
|
|
40
|
+
bump que consome a 2.15.0.
|
|
41
|
+
|
|
42
|
+
### Átomos t.* como schema de input de action (sdk) — 2.14.0
|
|
43
|
+
Apontada pelo projeto `fieldnotes` (exercício e2e, jul/2026; aceita na triagem). Veredito:
|
|
44
|
+
a capacidade JÁ EXISTIA — `t.slug().zod()` devolve o schema zod do tipo lógico — e o
|
|
45
|
+
buraco real era descoberta. Promovida como CONTRATO: doc de actions mostra o uso e um
|
|
46
|
+
teste trava `.zod()` como superfície pública (não re-escreva regex do que o Opus valida).
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Status:** v0 — pacote único `@softize/opus` com subpath exports (core + adapters). Uma versão, sem semver por módulo.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Protocolo de actions de ponta a ponta para TypeScript. Declare uma vez — input, autorização, execução, auditoria, feedback — e deixe os adapters materializarem a action na interface, no cliente, no servidor e no log.
|
|
6
6
|
|
|
7
7
|
**O Opus não é framework.** Não tem ciclo de vida próprio. É o contrato comum que todas as camadas falam.
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ End-to-end action protocol for TypeScript. Declare once — input, authorization
|
|
|
15
15
|
- **Contrato, não feature.** Tudo que entra no core padroniza forma; tudo que faz trabalho usa lib externa.
|
|
16
16
|
- **Fail-closed por default.** Action sem `authorize` é negada. Audit default-on.
|
|
17
17
|
- **Adapters plugáveis.** Cada categoria é uma superfície (subpath) com interface fechada e drivers trocáveis.
|
|
18
|
-
- **
|
|
18
|
+
- **Declaração é a fonte; doc é projeção.** As declarações (`defineContract`, `bindAction`, `defineEntity`) são a fonte dos contratos; manifest, OpenAPI e a doc gerada são projeções. `docs/protocol.md` descreve o protocolo em 16 seções (15 + glossário).
|
|
19
19
|
|
|
20
20
|
## Superfícies
|
|
21
21
|
|
|
@@ -23,21 +23,29 @@ Um pacote (`@softize/opus`), uma versão. Cada categoria abaixo é um **subpath
|
|
|
23
23
|
|
|
24
24
|
| Superfície | Drivers | Propósito |
|
|
25
25
|
|---|---|---|
|
|
26
|
-
| `@softize/opus` (core) | — | Protocolo, `defineAction`, `defineEntity`, runtime |
|
|
27
|
-
| `@softize/opus/schema` | `/zod`, `/openapi` |
|
|
28
|
-
| `@softize/opus/server` | `/fastify` | HTTP
|
|
29
|
-
| `@softize/opus/client` | `/fetch` |
|
|
30
|
-
| `@softize/opus/ui` | `/react` | Componentes
|
|
31
|
-
| `@softize/opus/data` | `/kysely` |
|
|
32
|
-
| `@softize/opus/auth` | `/jwt`, `/better-auth` |
|
|
33
|
-
| `@softize/opus/audit` | `/console`, `/pg` |
|
|
34
|
-
| `@softize/opus/log` | `/pino` |
|
|
35
|
-
| `@softize/opus/queue` | `/bullmq` |
|
|
36
|
-
| `@softize/opus/events` | `/mitt` | EventBus
|
|
37
|
-
| `@softize/opus/scheduler` | `/node-cron` |
|
|
26
|
+
| `@softize/opus` (core) | — | Protocolo, `defineContract`, `bindAction`, `defineAction`, `defineEntity`, runtime |
|
|
27
|
+
| `@softize/opus/schema` | `/zod`, `/openapi` | Tipos lógicos (`t.*`) e gerador de OpenAPI |
|
|
28
|
+
| `@softize/opus/server` | `/fastify`, `/node` | Transporte HTTP e montagem de endpoints |
|
|
29
|
+
| `@softize/opus/client` | `/fetch` | Adapter de cliente para invocar actions |
|
|
30
|
+
| `@softize/opus/ui` | `/react`, `/meta`, `/docs` | Componentes, Provider e hooks (`useAction`, `useListAction`) |
|
|
31
|
+
| `@softize/opus/data` | `/kysely`, `/readonly-pool` | Adapter de dados (`ctx.db`, `ctx.repo`) |
|
|
32
|
+
| `@softize/opus/auth` | `/jwt`, `/better-auth` | Adapter de autenticação (`ctx.user`, `ctx.can`) |
|
|
33
|
+
| `@softize/opus/audit` | `/console`, `/pg` | Destinos de auditoria |
|
|
34
|
+
| `@softize/opus/log` | `/pino` | Adapter de log (console é o default no core) |
|
|
35
|
+
| `@softize/opus/queue` | `/bullmq` | Execução de jobs em segundo plano |
|
|
36
|
+
| `@softize/opus/events` | `/mitt` | EventBus em processo |
|
|
37
|
+
| `@softize/opus/scheduler` | `/node-cron` | Adapter de agendamento (ação iniciada por tempo) |
|
|
38
|
+
| `@softize/opus/storage` | `/fs`, `/s3` | Armazenamento de arquivos (experimental) |
|
|
39
|
+
| `@softize/opus/cache` | `/memory` | Cache de leitura para handlers (experimental) |
|
|
40
|
+
| `@softize/opus/ai` | `/anthropic` | Capability de IA generativa: `complete` e `extract` (experimental) |
|
|
41
|
+
| `@softize/opus/mcp` | — | Expõe actions `ai:enabled` como tools MCP para agentes externos |
|
|
42
|
+
| `@softize/opus/observability` | `/opentelemetry` | Traces e contexto de execução, com porta vendor-neutral no core |
|
|
43
|
+
| `@softize/opus/testing` | — | Harness de teste de actions pela fronteira do contrato (experimental) |
|
|
44
|
+
| `@softize/opus/dsl` | — | Expressões declarativas avaliadas em `load` e em consultas |
|
|
38
45
|
| `@softize/opus/seed` | — | Declaração e binding de datasets verificáveis operados pela CLI |
|
|
46
|
+
| `@softize/opus/vite` | — | Plugins Vite: runtime em modo design e auth de sessão fixa |
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
Os drivers são resolvidos por subpath ESM (no estilo do Drizzle): `@softize/opus/server/fastify`, `@softize/opus/data/kysely`.
|
|
41
49
|
|
|
42
50
|
## Quick start
|
|
43
51
|
|
|
@@ -80,10 +88,11 @@ await app.listen({ port: 3000 })
|
|
|
80
88
|
|
|
81
89
|
```
|
|
82
90
|
src/
|
|
83
|
-
core/ schema/ server/ client/ ui/
|
|
84
|
-
|
|
91
|
+
core/ schema/ server/ client/ ui/ data/ auth/ audit/
|
|
92
|
+
log/ queue/ events/ scheduler/ storage/ cache/ ai/ mcp/
|
|
93
|
+
observability/ testing/ dsl/ seed/ vite/
|
|
85
94
|
registry/ # scaffolds + skills Opus — geração e conhecimento do SDK, não runtime
|
|
86
|
-
bin/ # CLI (opus setup/gen/copy/check/seed/introspect/mcp) + libs
|
|
95
|
+
bin/ # CLI (opus create/setup/gen/copy/check/db/seed/pre-push/introspect/mcp) + libs
|
|
87
96
|
docs/
|
|
88
97
|
protocol.md # contrato completo (16 seções)
|
|
89
98
|
data-layer.md · seeds.md · releasing.md · code-style.md
|
|
@@ -108,7 +117,7 @@ O contrato não oferece reset/truncate, e `apply` precisa convergir quando repet
|
|
|
108
117
|
|
|
109
118
|
## Status
|
|
110
119
|
|
|
111
|
-
- **v0**:
|
|
120
|
+
- **v0**: 21 superfícies, suíte vitest com cobertura medida por `pnpm test:cov`. O threshold de 100% em `vitest.config.ts` é aspiracional e não faz parte do gate de release (ver [docs/releasing.md](docs/releasing.md)).
|
|
112
121
|
- **v1+**: drivers adicionais (Hono, Drizzle, ArkType, Vue, Inngest, Redis events), camada de entidades (`defineEntity`), Conversya migration plan.
|
|
113
122
|
|
|
114
123
|
## Desenvolvimento
|
package/bin/cli.mjs
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @softize/opus CLI —
|
|
3
|
+
* @softize/opus CLI — `opus <comando>`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* npx @softize/opus gen # gera manifest/openapi/docs/stubs
|
|
10
|
-
*
|
|
11
|
-
* Lê components.json do consumer pra resolver path destino (mesma config
|
|
12
|
-
* do shadcn). Default: <cwd>/src/components/action/<name>.tsx.
|
|
5
|
+
* Cobre o ciclo de um projeto Opus: bootstrap (`create`, `setup`), projeções a partir
|
|
6
|
+
* das declarações (`gen`, `copy`, `introspect`), gates (`check`, `pre-push`, `db`,
|
|
7
|
+
* `seed`) e o server MCP para agentes (`mcp`). `opus --help` lista os comandos;
|
|
8
|
+
* `opus <comando> --help` detalha cada um.
|
|
13
9
|
*/
|
|
14
10
|
|
|
15
|
-
import { promises as fs } from 'node:fs'
|
|
16
11
|
import path from 'node:path'
|
|
17
12
|
import { fileURLToPath } from 'node:url'
|
|
18
|
-
import {
|
|
19
|
-
canonicalProjectDirectory,
|
|
20
|
-
ensureProjectDirectory,
|
|
21
|
-
readProjectFile,
|
|
22
|
-
safeProjectPath,
|
|
23
|
-
writeProjectFileAtomically,
|
|
24
|
-
} from '@softize/base/project-path'
|
|
25
13
|
|
|
26
14
|
import { cmdGen, helpGen } from './lib/gen.mjs'
|
|
27
15
|
import { cmdDb } from './lib/db.mjs'
|
|
28
16
|
import { scanDir, emptyScanVerdict } from './lib/check.mjs'
|
|
17
|
+
import { log } from './lib/cli-shared.mjs'
|
|
29
18
|
import { checkCopyInventory, writeCopyInventory } from './lib/copy.mjs'
|
|
30
19
|
import { createMonorepo, createProject } from './lib/create.mjs'
|
|
31
20
|
import { initProject, repoRootOf, setupUiFoundation } from './lib/init.mjs'
|
|
@@ -38,149 +27,10 @@ const __dirname = path.dirname(__filename)
|
|
|
38
27
|
const PACKAGE_ROOT = path.resolve(__dirname, '..')
|
|
39
28
|
const REGISTRY_DIR = path.join(PACKAGE_ROOT, 'registry')
|
|
40
29
|
|
|
41
|
-
// =============================================================================
|
|
42
|
-
// Helpers
|
|
43
|
-
// =============================================================================
|
|
44
|
-
|
|
45
|
-
function log(level, msg) {
|
|
46
|
-
const colors = {
|
|
47
|
-
info: '\x1b[36m',
|
|
48
|
-
success: '\x1b[32m',
|
|
49
|
-
error: '\x1b[31m',
|
|
50
|
-
warn: '\x1b[33m',
|
|
51
|
-
}
|
|
52
|
-
const reset = '\x1b[0m'
|
|
53
|
-
console.log(`${colors[level]}${msg}${reset}`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function fileExists(p) {
|
|
57
|
-
try {
|
|
58
|
-
await fs.access(p)
|
|
59
|
-
return true
|
|
60
|
-
} catch {
|
|
61
|
-
return false
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function loadComponentsJson(cwd) {
|
|
66
|
-
const candidate = readProjectFile(cwd, 'components.json', { allowMissing: true })
|
|
67
|
-
if (!candidate.exists) {
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
return JSON.parse(candidate.content)
|
|
72
|
-
} catch (err) {
|
|
73
|
-
log('warn', `components.json existe mas não é JSON válido: ${err.message}`)
|
|
74
|
-
return null
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function resolveAlias(alias, fallback) {
|
|
79
|
-
if (typeof alias !== 'string') return fallback
|
|
80
|
-
// Pega só a parte após "@/" pq path é relativo a src/.
|
|
81
|
-
// Ex: "@/components" → "components".
|
|
82
|
-
if (alias.startsWith('@/')) {
|
|
83
|
-
return alias.slice(2)
|
|
84
|
-
}
|
|
85
|
-
return alias
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function resolveDestination(cwd, name) {
|
|
89
|
-
const components = await loadComponentsJson(cwd)
|
|
90
|
-
if (components === null) {
|
|
91
|
-
log(
|
|
92
|
-
'warn',
|
|
93
|
-
'Sem components.json. Usando default ./src/components/action/. Roda `npx shadcn init` se quiser configurar.',
|
|
94
|
-
)
|
|
95
|
-
return path.join('src/components/action', `${name}.tsx`)
|
|
96
|
-
}
|
|
97
|
-
const aliasComponents =
|
|
98
|
-
components.aliases?.components ?? '@/components'
|
|
99
|
-
const componentsRel = resolveAlias(aliasComponents, 'components')
|
|
100
|
-
return path.join('src', componentsRel, 'action', `${name}.tsx`)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function listTemplates() {
|
|
104
|
-
if (!(await fileExists(REGISTRY_DIR))) {
|
|
105
|
-
log('error', `Registry não encontrado em ${REGISTRY_DIR}.`)
|
|
106
|
-
process.exit(1)
|
|
107
|
-
}
|
|
108
|
-
const files = await fs.readdir(REGISTRY_DIR)
|
|
109
|
-
return files
|
|
110
|
-
.filter((f) => f.endsWith('.tsx') || f.endsWith('.ts'))
|
|
111
|
-
.map((f) => f.replace(/\.(tsx|ts)$/, ''))
|
|
112
|
-
.sort()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
30
|
// =============================================================================
|
|
116
31
|
// Commands
|
|
117
32
|
// =============================================================================
|
|
118
33
|
|
|
119
|
-
async function cmdList() {
|
|
120
|
-
const templates = await listTemplates()
|
|
121
|
-
if (templates.length === 0) {
|
|
122
|
-
log(
|
|
123
|
-
'info',
|
|
124
|
-
'\nSem templates de cópia (os componentes de UI agora são LIB, não se copiam):\n' +
|
|
125
|
-
' • importe de `@softize/opus/ui/react` (Button, ActionForm, DropdownMenu…)\n' +
|
|
126
|
-
' • descubra o catálogo via `opus mcp` → tool `opus_list_components`\n',
|
|
127
|
-
)
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
log('info', `\n@softize/opus templates disponíveis:\n`)
|
|
131
|
-
for (const t of templates) {
|
|
132
|
-
console.log(` ${t}`)
|
|
133
|
-
}
|
|
134
|
-
console.log('')
|
|
135
|
-
log('info', `Adicionar: npx @softize/opus add <nome>\n`)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function cmdAdd(name, options) {
|
|
139
|
-
if (typeof name !== 'string' || name.length === 0) {
|
|
140
|
-
log('error', 'Uso: npx @softize/opus add <name>')
|
|
141
|
-
process.exit(1)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const templates = await listTemplates()
|
|
145
|
-
if (!templates.includes(name)) {
|
|
146
|
-
log('error', `Template "${name}" não existe.`)
|
|
147
|
-
console.log(`\nDisponíveis: ${templates.join(', ')}\n`)
|
|
148
|
-
process.exit(1)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const cwd = canonicalProjectDirectory(process.cwd())
|
|
152
|
-
const src = path.join(REGISTRY_DIR, `${name}.tsx`)
|
|
153
|
-
const dest = await resolveDestination(cwd, name)
|
|
154
|
-
safeProjectPath(cwd, dest)
|
|
155
|
-
const current = readProjectFile(cwd, dest, { allowMissing: true })
|
|
156
|
-
|
|
157
|
-
if (current.exists) {
|
|
158
|
-
if (options.force !== true) {
|
|
159
|
-
log(
|
|
160
|
-
'warn',
|
|
161
|
-
`Já existe em ${dest} — passa --force pra sobrescrever.`,
|
|
162
|
-
)
|
|
163
|
-
process.exit(1)
|
|
164
|
-
}
|
|
165
|
-
log('warn', `Sobrescrevendo ${dest}`)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const parents = []
|
|
169
|
-
let parent = path.dirname(dest)
|
|
170
|
-
while (parent !== '.') {
|
|
171
|
-
parents.unshift(parent)
|
|
172
|
-
parent = path.dirname(parent)
|
|
173
|
-
}
|
|
174
|
-
for (const directory of parents) ensureProjectDirectory(cwd, directory)
|
|
175
|
-
const content = await fs.readFile(src, 'utf-8')
|
|
176
|
-
writeProjectFileAtomically(cwd, dest, content, { exists: current.exists, content: current.content })
|
|
177
|
-
|
|
178
|
-
log('success', `✓ Criado ${dest}`)
|
|
179
|
-
console.log(
|
|
180
|
-
'\n Edita à vontade — esse arquivo é seu agora. Próximas runs com --force\n sobrescrevem suas mudanças.\n',
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
34
|
async function cmdCheck(dir) {
|
|
185
35
|
const root = path.resolve(process.cwd(), dir ?? '.')
|
|
186
36
|
const rel = path.relative(process.cwd(), root) || '.'
|
|
@@ -392,6 +242,7 @@ async function cmdIntrospect(dir, flags) {
|
|
|
392
242
|
function parseArgs(argv) {
|
|
393
243
|
const args = argv.slice(2)
|
|
394
244
|
const positional = []
|
|
245
|
+
// `force` sobrevive só por compatibilidade: gen aceita e ignora.
|
|
395
246
|
const flags = { force: false, help: false }
|
|
396
247
|
// Flags com valor — pega o próximo arg.
|
|
397
248
|
const VALUE_FLAGS = new Set(['--config', '--output', '--profile', '--scope'])
|
|
@@ -445,35 +296,49 @@ async function main() {
|
|
|
445
296
|
console.log(`
|
|
446
297
|
@softize/opus CLI
|
|
447
298
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
introspect [dir]
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
299
|
+
Uso: opus <comando> [argumentos] [flags]
|
|
300
|
+
opus <comando> --help detalha cada comando.
|
|
301
|
+
|
|
302
|
+
Bootstrap
|
|
303
|
+
create <dir> Cria um app Opus canônico (vite + react + UI + domínio-exemplo).
|
|
304
|
+
Dentro de um workspace cria só o app; --monorepo cria a raiz do workspace.
|
|
305
|
+
setup Reconcilia opus.json e os artefatos Opus versionados (skills, hooks,
|
|
306
|
+
instruções) sem sobrescrever o que é seu.
|
|
307
|
+
|
|
308
|
+
Projeções a partir das declarações
|
|
309
|
+
gen Gera manifest, openapi, docs e stubs a partir do opus.config.ts.
|
|
310
|
+
copy [dir] Gera o inventário semântico de copy dos contratos; --check só confere.
|
|
311
|
+
introspect [dir] Mostra o modelo da estrutura (actions/reactions/schedules + wiring); --json.
|
|
312
|
+
|
|
313
|
+
Gates (exit ≠ 0 quando há violação)
|
|
314
|
+
check [dir] Valida as convenções das actions e da UI (opus check --help lista as regras).
|
|
315
|
+
pre-push [dir] Gate do hook Git: o mesmo check; \`pre-push materialization\` confere só
|
|
316
|
+
se os artefatos materializados estão atualizados.
|
|
317
|
+
db <subcomando> check (drift entidade ↔ banco), migrate (aplica o schema idempotente) e
|
|
318
|
+
scaffold (rascunho a partir do diff).
|
|
319
|
+
seed <subcomando> list, check, plan, apply e verify de seeds estruturados.
|
|
320
|
+
|
|
321
|
+
Agentes
|
|
322
|
+
mcp Sobe o server MCP (introspect, check e scaffold de action) via stdio.
|
|
323
|
+
|
|
324
|
+
Flags
|
|
325
|
+
--help, -h Ajuda do comando.
|
|
326
|
+
--check (copy) Compara sem escrever; falha se o inventário estiver ausente ou
|
|
327
|
+
desatualizado.
|
|
328
|
+
--json (introspect, seed) Saída estruturada em JSON.
|
|
329
|
+
--monorepo (create) Cria a raiz do workspace em vez de um app.
|
|
330
|
+
--config <path> (gen, db, seed) Caminho do opus.config.ts. Default: ./opus.config.ts.
|
|
331
|
+
--output <path> (gen) Pasta de saída. Default: a do config, ou ./.gen.
|
|
332
|
+
--profile <nome> (seed) Perfil do dataset. Default: o defaultProfile do seed.
|
|
333
|
+
--scope <nome> (seed) Escopo explícito dos dados. Alternativa: OPUS_SEED_SCOPE.
|
|
334
|
+
--force, -f (gen) Aceita por compatibilidade; sem efeito, o gen sempre sobrescreve
|
|
335
|
+
a saída.
|
|
336
|
+
|
|
337
|
+
Exemplos
|
|
338
|
+
npx @softize/opus create apps/portal
|
|
476
339
|
npx @softize/opus gen --config ./apps/api/opus.config.ts
|
|
340
|
+
npx @softize/opus check src
|
|
341
|
+
npx @softize/opus db migrate
|
|
477
342
|
`)
|
|
478
343
|
return
|
|
479
344
|
}
|
|
@@ -522,29 +387,6 @@ o gate universal de copy está habilitado; Maestro continua opcional.
|
|
|
522
387
|
return
|
|
523
388
|
}
|
|
524
389
|
|
|
525
|
-
if (command === 'list') {
|
|
526
|
-
await cmdList()
|
|
527
|
-
return
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (command === 'add') {
|
|
531
|
-
if (flags.help) {
|
|
532
|
-
console.log(`
|
|
533
|
-
@softize/opus add <name>
|
|
534
|
-
|
|
535
|
-
Copia o template registry/<name>.tsx pra src/components/action/<name>.tsx
|
|
536
|
-
(ou ao alias resolvido via components.json).
|
|
537
|
-
|
|
538
|
-
Flags:
|
|
539
|
-
--force, -f Sobrescreve existente
|
|
540
|
-
`)
|
|
541
|
-
return
|
|
542
|
-
}
|
|
543
|
-
const [name] = rest
|
|
544
|
-
await cmdAdd(name, flags)
|
|
545
|
-
return
|
|
546
|
-
}
|
|
547
|
-
|
|
548
390
|
if (command === 'gen') {
|
|
549
391
|
if (flags.help) {
|
|
550
392
|
helpGen()
|
|
@@ -559,18 +401,31 @@ Flags:
|
|
|
559
401
|
console.log(`
|
|
560
402
|
@softize/opus check [dir]
|
|
561
403
|
|
|
562
|
-
Valida as convenções
|
|
563
|
-
\`defineAction\` e o split \`defineContract\`+\`bindAction
|
|
564
|
-
|
|
565
|
-
|
|
404
|
+
Valida as convenções do projeto lendo o source (TS compiler API), sem executar nada.
|
|
405
|
+
Enxerga \`defineAction\` e o split \`defineContract\`+\`bindAction\`, e varre os
|
|
406
|
+
arquivos de UI do projeto (fora de node_modules, builds, testes e fixtures).
|
|
407
|
+
|
|
408
|
+
Regras das actions:
|
|
409
|
+
action-name — name no formato <resource>.<verb> (minúsculo, ponto)
|
|
410
|
+
kind — kind ∈ simple|form|list|view
|
|
566
411
|
field-order — identidade→docs→input/output→authorize→handler→comportamento
|
|
567
412
|
export — defineAction/defineContract/bindAction como \`export const\`
|
|
568
413
|
requires-sem-authorize — \`requires\` é declarativo (o runtime não o executa);
|
|
569
|
-
action com requires precisa de authorize
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
414
|
+
action com requires precisa de authorize no contrato ou no
|
|
415
|
+
binding (o join contrato↔bind é cross-file)
|
|
416
|
+
|
|
417
|
+
Regras da UI (@softize/opus/ui):
|
|
418
|
+
ui-structure — anatomia pública das superfícies compostas (ADR 0005):
|
|
419
|
+
filho permitido dentro do pai correto
|
|
420
|
+
ui-semantic-api — API semântica: \`context\` + \`variant\`; reprova aliases de
|
|
421
|
+
migração e \`tone\` em novos dicionários
|
|
422
|
+
removed-ui-token — token público removido (ring-edge, shadow-card…) com a
|
|
423
|
+
substituição indicada
|
|
424
|
+
unpaired-ui-surface — superfície sem o foreground do par (bg-card sem
|
|
425
|
+
text-card-foreground, bg-popover sem text-popover-foreground)
|
|
426
|
+
|
|
427
|
+
Exit ≠ 0 se houver violação. Sem nenhuma action: projeto marcado (opus.json) passa
|
|
428
|
+
vacuamente; sem marcador, falha — gate vazio não é aprovação. Default dir: cwd.
|
|
574
429
|
`)
|
|
575
430
|
return
|
|
576
431
|
}
|
|
@@ -600,6 +455,22 @@ Flags:
|
|
|
600
455
|
}
|
|
601
456
|
|
|
602
457
|
if (command === 'pre-push') {
|
|
458
|
+
if (flags.help) {
|
|
459
|
+
console.log(`
|
|
460
|
+
@softize/opus pre-push [dir | materialization]
|
|
461
|
+
|
|
462
|
+
Gate que o hook Git instalado por \`opus setup\` executa antes de cada push, também
|
|
463
|
+
utilizável à mão:
|
|
464
|
+
pre-push [dir] O mesmo \`opus check [dir]\` (convenções das actions e da UI).
|
|
465
|
+
Default dir: cwd.
|
|
466
|
+
pre-push materialization Confere se os artefatos Opus materializados no repo (skills,
|
|
467
|
+
hooks, instruções) estão atualizados para a versão adotada.
|
|
468
|
+
|
|
469
|
+
O hook roda \`copy --check\`, depois \`pre-push materialization\` na raiz e \`check\` em cada
|
|
470
|
+
app com opus.json; qualquer um com exit ≠ 0 bloqueia o push.
|
|
471
|
+
`)
|
|
472
|
+
return
|
|
473
|
+
}
|
|
603
474
|
if (rest[0] === 'materialization') await cmdMaterializationCheck()
|
|
604
475
|
else await cmdCheck(rest[0] ?? '.')
|
|
605
476
|
return
|