@softize/opus 13.0.0 → 13.1.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/PROMOTED.md +46 -0
  3. package/README.md +28 -19
  4. package/bin/cli.mjs +87 -216
  5. package/bin/lib/cli-shared.mjs +131 -0
  6. package/bin/lib/db.mjs +16 -74
  7. package/bin/lib/gen-openapi.mjs +3 -3
  8. package/bin/lib/gen-runner.mjs +1 -1
  9. package/bin/lib/gen.mjs +14 -69
  10. package/bin/lib/mcp.mjs +3 -1
  11. package/bin/lib/seed.mjs +5 -62
  12. package/docs/ownership-vs-shadcn-lock.md +2 -3
  13. package/docs/protocol.md +7 -7
  14. package/docs/releasing.md +8 -2
  15. package/package.json +7 -3
  16. package/registry/templates/app/package.json +1 -1
  17. package/registry/templates/app/src/main.tsx +4 -4
  18. package/src/audit/drivers/console.ts +1 -0
  19. package/src/auth/drivers/better-auth.ts +1 -0
  20. package/src/auth/drivers/jwt.ts +1 -0
  21. package/src/cache/drivers/memory.ts +1 -0
  22. package/src/client/drivers/fetch.ts +2 -1
  23. package/src/core/actions.ts +6 -1
  24. package/src/core/audit.ts +9 -3
  25. package/src/core/contracts.ts +7 -0
  26. package/src/core/domain.ts +1 -1
  27. package/src/core/errors.ts +18 -15
  28. package/src/core/index.ts +4 -2
  29. package/src/core/package-version.ts +26 -0
  30. package/src/core/reactions.ts +1 -1
  31. package/src/core/runtime.ts +33 -23
  32. package/src/core/schedules.ts +1 -1
  33. package/src/core/types.ts +2 -2
  34. package/src/dsl/eval.ts +2 -2
  35. package/src/dsl/kysely.ts +2 -2
  36. package/src/dsl/loads.ts +1 -1
  37. package/src/dsl/parser.ts +5 -5
  38. package/src/events/drivers/mitt.ts +1 -0
  39. package/src/mcp/index.ts +2 -1
  40. package/src/observability/drivers/opentelemetry.ts +1 -0
  41. package/src/queue/drivers/bullmq.ts +3 -3
  42. package/src/scheduler/drivers/node-cron.ts +3 -2
  43. package/src/scheduler/every.ts +7 -7
  44. package/src/schema/openapi.ts +3 -3
  45. package/src/seed/index.ts +29 -0
  46. package/src/server/drivers/fastify.ts +5 -2
  47. package/src/server/drivers/node.ts +9 -6
  48. package/src/server/index.ts +3 -1
  49. package/src/storage/drivers/fs.ts +1 -0
  50. package/src/testing/index.ts +3 -3
  51. package/src/ui/components/patterns/confirm.tsx +2 -2
  52. package/src/ui/components/patterns/content-header.tsx +7 -1
  53. package/src/ui/components/patterns/data-state.tsx +1 -1
  54. package/src/ui/components/patterns/dock.tsx +20 -3
  55. package/src/ui/components/patterns/form.tsx +12 -8
  56. package/src/ui/components/patterns/list.tsx +4 -4
  57. package/src/ui/components/patterns/page.tsx +19 -1
  58. package/src/ui/components/patterns/shell-nav.tsx +10 -3
  59. package/src/ui/components/patterns/sidebar.tsx +17 -6
  60. package/src/ui/components/patterns/trigger.tsx +14 -16
  61. package/src/ui/components/patterns/view.tsx +26 -17
  62. package/src/ui/components/primitives/alert.tsx +11 -5
  63. package/src/ui/components/primitives/ask.tsx +3 -3
  64. package/src/ui/components/primitives/badge.tsx +11 -6
  65. package/src/ui/components/primitives/breadcrumb.tsx +2 -2
  66. package/src/ui/components/primitives/button.tsx +16 -3
  67. package/src/ui/components/primitives/calendar.tsx +28 -2
  68. package/src/ui/components/primitives/carousel.tsx +3 -3
  69. package/src/ui/components/primitives/chat.tsx +1 -1
  70. package/src/ui/components/primitives/checkbox.tsx +1 -1
  71. package/src/ui/components/primitives/command.tsx +2 -2
  72. package/src/ui/components/primitives/control.ts +12 -0
  73. package/src/ui/components/primitives/copyable.tsx +1 -1
  74. package/src/ui/components/primitives/dialog.tsx +12 -7
  75. package/src/ui/components/primitives/dot.tsx +5 -0
  76. package/src/ui/components/primitives/drawer.tsx +10 -3
  77. package/src/ui/components/primitives/field.tsx +3 -3
  78. package/src/ui/components/primitives/icon-picker.tsx +3 -1
  79. package/src/ui/components/primitives/input-group.tsx +1 -1
  80. package/src/ui/components/primitives/input-otp.tsx +1 -1
  81. package/src/ui/components/primitives/input.tsx +2 -2
  82. package/src/ui/components/primitives/progress.tsx +32 -3
  83. package/src/ui/components/primitives/radio-group.tsx +1 -1
  84. package/src/ui/components/primitives/resizable.tsx +3 -1
  85. package/src/ui/components/primitives/select.tsx +5 -5
  86. package/src/ui/components/primitives/slider.tsx +5 -1
  87. package/src/ui/components/primitives/sonner.tsx +3 -0
  88. package/src/ui/components/primitives/switch.tsx +1 -0
  89. package/src/ui/components/primitives/tabs.tsx +1 -0
  90. package/src/ui/components/primitives/textarea.tsx +1 -1
  91. package/src/ui/components/primitives/toggle.tsx +1 -1
  92. package/src/ui/components/primitives/tooltip.tsx +1 -0
  93. package/src/ui/docs/changelog.tsx +1 -1
  94. package/src/ui/docs/content/action-form.md +10 -3
  95. package/src/ui/docs/content/action-list.md +10 -1
  96. package/src/ui/docs/content/action-trigger.md +8 -1
  97. package/src/ui/docs/content/action-view.md +9 -2
  98. package/src/ui/docs/content/ask.md +11 -0
  99. package/src/ui/docs/content/calendar.md +13 -0
  100. package/src/ui/docs/content/card.md +26 -0
  101. package/src/ui/docs/content/chat.md +20 -0
  102. package/src/ui/docs/content/cli.md +71 -19
  103. package/src/ui/docs/content/composer.md +15 -0
  104. package/src/ui/docs/content/content.md +15 -0
  105. package/src/ui/docs/content/copyable.md +8 -0
  106. package/src/ui/docs/content/detail.md +19 -1
  107. package/src/ui/docs/content/dictionary-value.md +9 -2
  108. package/src/ui/docs/content/dock.md +8 -0
  109. package/src/ui/docs/content/dot.md +8 -0
  110. package/src/ui/docs/content/empty.md +2 -2
  111. package/src/ui/docs/content/getting-started.md +2 -2
  112. package/src/ui/docs/content/icon-picker.md +11 -0
  113. package/src/ui/docs/content/label.md +7 -0
  114. package/src/ui/docs/content/menu.md +6 -0
  115. package/src/ui/docs/content/metric-card.md +13 -0
  116. package/src/ui/docs/content/page.md +8 -0
  117. package/src/ui/docs/content/popover.md +6 -0
  118. package/src/ui/docs/content/progress.md +8 -11
  119. package/src/ui/docs/content/select.md +5 -5
  120. package/src/ui/docs/content/semantic-context.md +2 -2
  121. package/src/ui/docs/content/sidebar.md +14 -8
  122. package/src/ui/docs/content/skeleton.md +6 -0
  123. package/src/ui/docs/content/split.md +21 -0
  124. package/src/ui/docs/content/textarea.md +7 -0
  125. package/src/ui/docs/content/tokens.md +4 -4
  126. package/src/ui/docs/content/truncate.md +8 -0
  127. package/src/ui/docs/content/ui.md +14 -0
  128. package/src/ui/docs/doc-client.tsx +5 -5
  129. package/src/ui/docs/doc.tsx +26 -14
  130. package/src/ui/docs/registry.tsx +5 -5
  131. package/src/ui/docs/standalone.tsx +2 -2
  132. package/src/ui/drivers/react.tsx +17 -12
  133. package/src/ui/lib/action-errors.ts +45 -0
  134. package/src/ui/lib/zod-pt-br.ts +31 -4
  135. package/src/ui/meta.ts +3 -3
  136. package/src/ui/react.tsx +2 -0
  137. package/src/ui/theme.css +10 -8
  138. package/src/vite/design.ts +6 -18
package/CHANGELOG.md CHANGED
@@ -7,6 +7,53 @@ 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
+ ## 13.1.0 — 2026-09-08
11
+
12
+ Erros deixam de falar inglês e de vazar detalhe interno. `normalizeError` devolve a microcopy fixa
13
+ "Não foi possível concluir a operação. Tente novamente." em `internal.unhandled`; o texto da
14
+ exceção original vai para o logger do runtime e para `cause`, e o servidor não serializa `cause` de
15
+ erro interno na resposta. Todas as mensagens de `ActionError` do runtime, do harness de `testing`,
16
+ dos drivers `node` e `fetch`, do `AuditEmitter`, do scheduler, da DSL e do modo design passam a
17
+ pt-BR com os códigos inalterados; o OpenAPI descreve as respostas como "Sucesso", "Erro do cliente"
18
+ e "Erro do servidor". Na UI, `humanizeActionError` (`lib/action-errors.ts`) concentra a allowlist
19
+ de erros de negócio que `ActionTrigger` já usava e passa a valer em `ActionForm` e `ActionView`:
20
+ o código técnico não aparece mais como título, e as mensagens Zod em pt-BR cobrem todos os códigos
21
+ do zod v3.
22
+
23
+ Button sem `context` resolve `neutral` para `outline`, `ghost` e `link` (e `primary` para `solid` e
24
+ `subtle`), o que devolve ao `ghost` a cor de texto que ele tinha antes da migração semântica.
25
+ `Calendar` nasce em pt-BR (locale, dropdown de mês e rótulos de navegação). `Progress` e
26
+ `SurfaceStatus` aceitam `context`; `ActionView.loading` aceita boolean. O anel de foco é um só
27
+ (`focusRing`), os tokens `*-destructive` deram lugar à família `context-danger` nos controles de
28
+ formulário, Dialog e Drawer compartilham o overlay e o thumb do Slider usa `bg-background`.
29
+ Textos em inglês de Command, Drawer, Breadcrumb, Carousel, do site e dos exemplos foram
30
+ traduzidos; o site deixa de sobrescrever o fundo do tema. Breadcrumb, carousel, progress,
31
+ resizable e slider passam a ejetados no `registry.lock.json`.
32
+
33
+ Descontinuados, com aviso no editor e sem mudança de comportamento: as variantes legadas
34
+ (`default`, `secondary`, `destructive`, `success`, `warning`, `info`), `confirm()`, `ConfirmHost`,
35
+ `FieldSpec.hint` (use `help`), `ShellNav*` (use `SidebarNav`) e `TbdlibProvider`, renomeado para
36
+ `OpusProvider`. `'danger'` deixa de ser aceito como *variante* legada de Badge e Alert no tipo (era
37
+ um contexto disfarçado): declare `context="danger"`. O botão de confirmar do `ActionTrigger` passa a
38
+ ser sempre `solid` — `danger` quando a action é `destructive`, senão o `context` do gatilho — e não
39
+ herda mais o `variant` do gatilho; a permissão negada (`authorization`/`authentication`) entra na
40
+ 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
41
+ pacote, e o servidor MCP reporta a versão real; a marca `tbdlib` saiu do código.
42
+
43
+ O CLI perde os comandos `add` e `list`, mortos desde que os componentes viraram biblioteca, e
44
+ ganha um `--help` fiel ao dispatch: subcomandos de `db` (`check`, `migrate`, `scaffold`), flags
45
+ `--json` e `--monorepo`, `check --help` com as nove regras e `pre-push --help`. `gen`, `db` e
46
+ `seed` compartilham `bin/lib/cli-shared.mjs`, e `opus.config.ts` ausente produz a mesma mensagem
47
+ nos três. A documentação de `cli.md` cobre `opus copy`, o hook de pré-push e as flags; quinze
48
+ páginas de componentes ganham tabela de propriedades e os hooks `useListAction`,
49
+ `useFormAction`, `useViewAction`, `useTriggerAction` e `useDicts` aparecem nas páginas que os
50
+ usam. `PROMOTED.md` viaja no pacote.
51
+
52
+ **Atenção:** a peer `react`/`react-dom` passa a `^19.0.0`. O suporte a React 18 nunca funcionou
53
+ de fato, porque os componentes dependem de `ref` como prop; consumidores em 19 não precisam
54
+ fazer nada. O default de `ghost` sem `context` muda de cor: quem quiser o azul de antes declara
55
+ `context="primary"`.
56
+
10
57
  ## 13.0.0 — 2026-09-07
11
58
 
12
59
  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
- End-to-end action protocol for TypeScript. Declare once — input, authorization, execution, audit, feedback — and let adapters materialize it across UI, client, server, and log.
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
- - **Doc é fonte da verdade.** `docs/protocol.md` 16 seções fechadas (15 + glossário).
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` | Logical types (`t.*`) + OpenAPI generator |
28
- | `@softize/opus/server` | `/fastify` | HTTP transport + endpoint mount |
29
- | `@softize/opus/client` | `/fetch` | Client adapter pra invocar actions |
30
- | `@softize/opus/ui` | `/react` | Componentes + Provider + hooks (`useAction`, `useListAction`) |
31
- | `@softize/opus/data` | `/kysely` | Data adapter (`ctx.db`, `ctx.repo`) |
32
- | `@softize/opus/auth` | `/jwt`, `/better-auth` | Auth adapter (`ctx.user`, `ctx.can`) |
33
- | `@softize/opus/audit` | `/console`, `/pg` | Audit sinks |
34
- | `@softize/opus/log` | `/pino` | Logger adapter (console default no core) |
35
- | `@softize/opus/queue` | `/bullmq` | Background job execution |
36
- | `@softize/opus/events` | `/mitt` | EventBus (in-process) |
37
- | `@softize/opus/scheduler` | `/node-cron` | Schedule adapter (ação iniciada por tempo) |
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
- Drivers via subpath ESM (estilo Drizzle): `@softize/opus/server/fastify`, `@softize/opus/data/kysely`.
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
- data/ auth/ audit/ log/ queue/ events/ scheduler/ dsl/
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**: 12 superfícies, 430 tests, 100% coverage.
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 — copia templates do registry pro consumer.
3
+ * @softize/opus CLI — `opus <comando>`.
4
4
  *
5
- * Uso:
6
- * npx @softize/opus add <name> # copia registry/<name>.tsx
7
- * npx @softize/opus add <name> --force # sobrescreve se existe
8
- * npx @softize/opus list # lista templates disponíveis
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
- Comandos:
449
- create <dir> Scaffolda um app opus-based canônico (vite+react+ui+domínio-exemplo);
450
- detecta workspace (app em monorepo); --monorepo cria a RAIZ do workspace
451
- setup Reconcilia opus.json e os artefatos Opus versionados para Claude/Codex
452
- add <name> Copia template do catálogo empacotado pro projeto
453
- list Lista templates disponíveis
454
- gen Gera manifest/openapi/docs/stubs a partir do opus.config.ts
455
- copy [dir] Gera o inventário semântico de copy dos contratos; --check só confere
456
- check [dir] Valida as convenções das actions (régua de padrão; exit ≠ 0 se violar)
457
- pre-push Gate Git: freshness dos artefatos + convenções das actions
458
- db <verbo> Comandos de banco. v1: db check (drift-check entidade ↔ banco)
459
- seed <verbo> Lista, valida, planeja, aplica e verifica seeds estruturados
460
- introspect [dir] Modelo da estrutura (actions/reactions/schedules + wiring); --json
461
- mcp Server MCP (introspect/check/scaffold de action) — agentes via mcp_config
462
- help Mostra esta mensagem
463
-
464
- Flags:
465
- --force, -f Sobrescreve arquivo existente
466
- --check Não escreve; falha se o inventário de copy estiver ausente/desatualizado
467
- --config <path> (gen) Caminho do opus.config.ts
468
- --output <path> (gen) Pasta de saída
469
- --profile <name> (seed) Perfil do dataset
470
- --scope <name> (seed) Escopo explícito dos dados
471
-
472
- Exemplos:
473
- npx @softize/opus add action-form
474
- npx @softize/opus add action-list --force
475
- npx @softize/opus list
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 das actions (estático, TS compiler API). Enxerga
563
- \`defineAction\` e o split \`defineContract\`+\`bindAction\`:
564
- action-name — <resource>.<verb> (minúsculo, ponto)
565
- kind — simple|form|list|view
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 (no contrato
570
- ou no binding o join contrato↔bind é cross-file)
571
-
572
- Exit 0 se houver violação OU se não achar action nenhuma (gate vazio não
573
- passa verde). Default dir: cwd.
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