@softize/opus 28.0.0 → 29.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/bin/cli.mjs +68 -3
  3. package/bin/lib/copy.mjs +505 -67
  4. package/bin/lib/materialize.mjs +99 -1
  5. package/docs/adr/0028-schedules-run-actions-as-the-system-by-action-opt-in.md +1 -1
  6. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  7. package/docs/adr/0061-claude-md-imports-agents-md.md +41 -0
  8. package/docs/adr/README.md +2 -0
  9. package/docs/code-style.md +47 -8
  10. package/docs/protocol.md +1 -1
  11. package/docs/releasing.md +7 -0
  12. package/package.json +1 -1
  13. package/registry/instructions/opus.md +3 -1
  14. package/registry/skills/build-opus-ui/SKILL.md +5 -3
  15. package/registry/skills/maintain-opus-docs/SKILL.md +2 -1
  16. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +9 -0
  17. package/registry/skills/maintain-opus-docs/references/evaluations.md +8 -0
  18. package/registry/templates/app/pnpm-workspace.yaml +1 -1
  19. package/src/core/system-actor.ts +1 -1
  20. package/src/ui/components/patterns/list.tsx +1 -1
  21. package/src/ui/docs/content/action-list.md +1 -1
  22. package/src/ui/docs/content/actions.md +1 -1
  23. package/src/ui/docs/content/auth.md +3 -3
  24. package/src/ui/docs/content/cli.md +6 -0
  25. package/src/ui/docs/content/communication.md +10 -8
  26. package/src/ui/docs/content/events.md +13 -13
  27. package/src/ui/docs/content/installation.md +4 -0
  28. package/src/ui/docs/content/log.md +1 -1
  29. package/src/ui/docs/content/queue.md +2 -2
  30. package/src/ui/docs/content/router.md +1 -1
  31. package/src/ui/docs/content/scheduler.md +15 -15
  32. package/src/ui/docs/content/testing.md +4 -4
  33. package/src/vite/design.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,64 @@ 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
+ ## 29.0.0 — 2026-10-15
11
+
12
+ O `CLAUDE.md` passa a importar o `AGENTS.md`, e as declarações de copy passam a ser ancoradas pelo
13
+ conteúdo, não pela linha. A seção **Breaking** traz a única migração obrigatória, que atinge só quem
14
+ mantém instruções no `CLAUDE.md` que não estão no `AGENTS.md`.
15
+
16
+ ### Breaking
17
+
18
+ `opus setup` mantém o bloco do Opus só no `AGENTS.md` e grava o `CLAUDE.md` como a linha
19
+ `@AGENTS.md`, que importa as instruções para o Claude Code (ADR 0061, no mesmo formato da ADR 0006 da
20
+ Base). Na conversão, o `setup` remove do `CLAUDE.md` o bloco do Opus e descarta o texto do projeto
21
+ que repete o do `AGENTS.md`. Texto que só existe no `CLAUDE.md` é preservado, e o `setup` e o
22
+ `opus pre-push materialization` reprovam até que ele vá para o `AGENTS.md`. Blocos de outros
23
+ pacotes ficam depois do import até que o pacote dono os remova: com uma Base anterior à ADR 0006, o
24
+ `CLAUDE.md` fica com o import e o bloco da Base.
25
+
26
+ Migre movendo para o `AGENTS.md` as instruções que estão só no `CLAUDE.md` e rodando `opus setup`.
27
+
28
+ ### Âncora de copy pelo conteúdo
29
+
30
+ - **`copy.dynamic` por trecho.** A declaração seleciona o diagnóstico por `source`, `field` e
31
+ `expression`, o trecho de código que o extrator não resolveu, e continua válida quando linhas
32
+ entram ou saem acima dela. Espaços e quebras de linha do trecho são normalizados e a vírgula
33
+ final antes de `)`, `]` ou `}` é ignorada, para a âncora sobreviver à reformatação. A declaração
34
+ vale para toda ocorrência do mesmo trecho no mesmo campo do arquivo, e `dynamicSurfaces` registra
35
+ as linhas atuais de cada uma. O diagnóstico de conteúdo traz o `field` e a `expression` prontos
36
+ para a declaração.
37
+ - **`copy.exemptions` e `copy.transforms` por texto.** Sem `line`, `source`, `role` e `text`
38
+ selecionam toda ocorrência desse texto com esse papel no arquivo.
39
+ - **Seletores por linha continuam aceitos.** Eles selecionam exatamente um alvo, são aplicados
40
+ antes dos seletores por conteúdo e geram um aviso no `opus copy`. `opus copy --reanchor` converte
41
+ o `base.json` agrupando as declarações por arquivo e campo, ou por arquivo, papel e texto. Um
42
+ grupo só é convertido quando tem uma declaração para cada alvo, para que a âncora por conteúdo não
43
+ cubra um alvo que ninguém declarou; dentro dele, declarações e alvos são pareados em ordem de
44
+ linha, e as que mudaram de linha são listadas para revisão. Declarações idênticas se fundem, e
45
+ duas com motivos diferentes para o mesmo conteúdo continuam por linha, já corrigida. Os seletores
46
+ gravados passam na extração do `opus copy`; a política da Base não é avaliada, então rode
47
+ `opus copy --check` e `base copy check` depois e revise o diff.
48
+ - **`dynamicSurfaces` no inventário.** Uma declaração por conteúdo aparece com `expression` e
49
+ `lines`, as linhas atuais de cada ocorrência, no lugar de `line`. Uma lista `lines` que cresce no
50
+ diff indica que o trecho passou a cobrir uma ocorrência nova.
51
+ - **Erros que dizem onde.** Cada seletor que deixa de encontrar seu alvo aparece na mesma execução,
52
+ com o arquivo, o campo, o trecho e os diagnósticos daquele campo ainda sem declaração, ou com o
53
+ aviso de que o arquivo não existe mais no escopo.
54
+
55
+ ### Instruções, documentação e changelog
56
+
57
+ - A instrução gerenciada do Opus passa a dizer que o inventário de copy guarda o hash de todo
58
+ arquivo JS/TS do escopo, inclusive scripts sem copy como `scripts/*.mjs`, e que alterar qualquer
59
+ um deles também exige `opus copy`.
60
+ - Os exemplos da documentação usam identificadores, nomes de action, domínios, eventos e permissões
61
+ em inglês (`ledger.close`, `ledger:close`, `invoice.issued`); rótulos, mensagens e comentários
62
+ continuam em pt-BR. O padrão editorial e a lista de verificação da skill `maintain-opus-docs`
63
+ passam a exigir isso, e um teste do repositório reprova identificadores com palavra em português.
64
+ - Entradas do changelog que já estão na `origin/main` do Opus não mudam mais: uma correção de
65
+ informação publicada entra como entrada nova. O pre-push e o `pnpm release` do repositório
66
+ reprovam a alteração.
67
+
10
68
  ## 28.0.0 — 2026-10-13
11
69
 
12
70
  ### Breaking
package/bin/cli.mjs CHANGED
@@ -15,7 +15,7 @@ import { cmdGen, helpGen } from './lib/gen.mjs'
15
15
  import { cmdDb } from './lib/db.mjs'
16
16
  import { scanDir, emptyScanVerdict } from './lib/check.mjs'
17
17
  import { log } from './lib/cli-shared.mjs'
18
- import { checkCopyInventory, writeCopyInventory } from './lib/copy.mjs'
18
+ import { checkCopyInventory, reanchorCopySelectors, writeCopyInventory } from './lib/copy.mjs'
19
19
  import { createMonorepo, createProject } from './lib/create.mjs'
20
20
  import { initProject, repoRootOf, setupUiFoundation } from './lib/init.mjs'
21
21
  import { introspect } from './lib/introspect.mjs'
@@ -63,9 +63,9 @@ async function cmdCheck(dir) {
63
63
  log('error', `✗ opus check: nenhuma action encontrada em ${rel} (${files} arquivos .ts); ${verdict.message}`)
64
64
  process.exit(1)
65
65
  }
66
- const viaContrato = contracts > 0 ? ` (${contracts} contrato(s))` : ''
66
+ const contractSuffix = contracts > 0 ? ` (${contracts} contrato(s))` : ''
67
67
  if (findings.length === 0) {
68
- log('success', `✓ opus check: ${actions} action(s)${viaContrato}; nenhuma violação encontrada.`)
68
+ log('success', `✓ opus check: ${actions} action(s)${contractSuffix}; nenhuma violação encontrada.`)
69
69
  return
70
70
  }
71
71
 
@@ -97,10 +97,28 @@ async function cmdMaterializationCheck() {
97
97
  async function cmdCopy(dir, flags) {
98
98
  const selected = path.resolve(process.cwd(), dir ?? '.')
99
99
  const root = (await repoRootOf(selected)) ?? selected
100
+ if (flags.reanchor) {
101
+ if (flags.check) {
102
+ log('error', '✗ opus copy: --reanchor grava o base.json e não combina com --check.')
103
+ process.exitCode = 1
104
+ return
105
+ }
106
+ if (!reportReanchor(reanchorCopySelectors(root))) {
107
+ process.exitCode = 1
108
+ return
109
+ }
110
+ }
100
111
  const result = flags.check ? checkCopyInventory(root) : writeCopyInventory(root)
101
112
  for (const diagnostic of result.diagnostics) {
102
113
  log('error', `✗ opus copy: ${diagnostic.source}:${diagnostic.line} — ${diagnostic.message}`)
103
114
  }
115
+ if (result.legacySelectors > 0) {
116
+ log(
117
+ 'warn',
118
+ `! opus copy: ${result.legacySelectors} seletor(es) em base.json ainda usam \`line\`, que quebra a cada edição acima dele; ` +
119
+ 'rode `opus copy --reanchor` para ancorá-los pelo conteúdo.',
120
+ )
121
+ }
104
122
  if (!result.ok) {
105
123
  if (result.reason === 'missing') {
106
124
  log('error', `✗ opus copy: inventário ausente em ${path.relative(root, result.path)}; rode \`opus copy\` e versione o resultado.`)
@@ -121,6 +139,40 @@ async function cmdCopy(dir, flags) {
121
139
  else log('success', `✓ opus copy: ${location} já estava atualizado (${summary}).`)
122
140
  }
123
141
 
142
+ function reportReanchor(result) {
143
+ if (!result.ok) {
144
+ for (const diagnostic of result.diagnostics) {
145
+ log('error', `✗ opus copy --reanchor: ${diagnostic.source}:${diagnostic.line} — ${diagnostic.message}`)
146
+ }
147
+ log('error', '✗ opus copy --reanchor: resolva os problemas de leitura acima antes de reancorar; base.json não foi alterado.')
148
+ return false
149
+ }
150
+ const { converted, realigned, relined, merged, unresolved } = result.report
151
+ const anchor = (item) => (item.anchor.expression === undefined ? `"${item.anchor.text}"` : `\`${item.anchor.expression}\``)
152
+ if (converted.length > 0) log('success', `✓ opus copy --reanchor: ${converted.length} seletor(es) passaram a ser ancorados pelo conteúdo.`)
153
+ // copy.dynamic identifica o alvo pelo campo; exceções e transformações, pelo papel do texto.
154
+ const target = (item) => (item.list === 'dynamic' ? `\`${item.selector.field}\`` : `(${item.selector.role})`)
155
+ if (realigned.length > 0) {
156
+ log('warn', `! opus copy --reanchor: ${realigned.length} âncora(s) quebradas pela mudança de linha foram convertidas; revise cada uma no diff:`)
157
+ for (const item of realigned) {
158
+ console.log(` copy.${item.list}[${item.index}] ${item.selector.source} ${target(item)} linha ${item.from} → ${item.to}: ${anchor(item)}`)
159
+ }
160
+ }
161
+ if (relined.length > 0) {
162
+ log('warn', `! opus copy --reanchor: ${relined.length} seletor(es) continuam por linha, com a linha corrigida; revise cada um no diff:`)
163
+ for (const item of relined) {
164
+ console.log(` copy.${item.list}[${item.index}] ${item.selector.source} linha ${item.from} → ${item.to}: ${item.reason}`)
165
+ }
166
+ }
167
+ if (merged.length > 0) log('success', `✓ opus copy --reanchor: ${merged.length} declaração(ões) repetida(s) fundida(s) com a idêntica.`)
168
+ if (unresolved.length > 0) {
169
+ log('warn', `! opus copy --reanchor: ${unresolved.length} seletor(es) continuam por linha:`)
170
+ for (const item of unresolved) console.log(` copy.${item.list}[${item.index}]: ${item.reason}`)
171
+ }
172
+ if (!result.changed) log('success', '✓ opus copy --reanchor: nada a converter em base.json.')
173
+ return true
174
+ }
175
+
124
176
  async function cmdCreate(dir, flags) {
125
177
  if (!dir) {
126
178
  log('error', 'Uso: opus create <dir> — o nome do diretório vira o nome do app (--monorepo: a raiz do workspace).')
@@ -273,6 +325,10 @@ function parseArgs(argv) {
273
325
  flags.json = true
274
326
  continue
275
327
  }
328
+ if (arg === '--reanchor') {
329
+ flags.reanchor = true
330
+ continue
331
+ }
276
332
  if (arg === '--monorepo') {
277
333
  flags.monorepo = true
278
334
  continue
@@ -334,6 +390,7 @@ Flags
334
390
  --help, -h Ajuda do comando.
335
391
  --check (copy) Compara sem escrever; falha se o inventário estiver ausente ou
336
392
  desatualizado.
393
+ --reanchor (copy) Ancora pelo conteúdo os seletores do base.json que usam linha.
337
394
  --json (introspect, seed) Saída estruturada em JSON.
338
395
  --monorepo (create) Cria a raiz do workspace em vez de um app.
339
396
  --config <path> (gen, db, seed) Caminho do opus.config.ts. Default: ./opus.config.ts.
@@ -483,6 +540,14 @@ comprova que o extrator executou sobre uma fonte.
483
540
 
484
541
  Flags:
485
542
  --check Compara sem escrever; falha em ausência, drift ou copy dinâmica
543
+ sem declaração.
544
+ --reanchor Converte os seletores de copy.dynamic, copy.exemptions e
545
+ copy.transforms que ainda usam \`line\` para seletores pelo
546
+ conteúdo, grava o base.json e regenera o inventário. Só
547
+ converte quando há uma declaração para cada alvo, para não
548
+ ampliar nenhuma; as que mudaram de linha são listadas para
549
+ revisão, e o que não dá para converter com segurança fica
550
+ como está. Depois, rode base copy check e revise o diff.
486
551
  `)
487
552
  return
488
553
  }