@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
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,24 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
11
11
|
> tinha ficado sem registro nenhum, o que é exatamente o caso que este arquivo existe
|
|
12
12
|
> pra cobrir.
|
|
13
13
|
|
|
14
|
+
## 8.9.0 — 2026-08-04
|
|
15
|
+
|
|
16
|
+
**O Opus passa a materializar e versionar suas próprias skills e garantias.** `opus setup`
|
|
17
|
+
agora projeta as cinco skills do SDK para Claude e Codex, instala o hook Stop, participa do
|
|
18
|
+
pre-push composto, cria o gate `opus.yml` e mantém um bloco próprio em `AGENTS.md` e
|
|
19
|
+
`CLAUDE.md`. Tudo viaja no Git e `opus check` recusa drift entre o pacote instalado,
|
|
20
|
+
`base.json` e os arquivos comitados. O Maestro deixa de ser requisito operacional.
|
|
21
|
+
|
|
22
|
+
O catálogo é `implement-opus-change`, `create-opus-action`, `test-opus-action`,
|
|
23
|
+
`build-opus-ui` e `upgrade-opus`. `create-opus-action` gera o split atual
|
|
24
|
+
`defineContract` + `bindAction`; a skill legada `create-action`, que ainda emitia handler
|
|
25
|
+
inline, foi removida.
|
|
26
|
+
|
|
27
|
+
**Migração:** depois do bump, rode `pnpm exec opus setup`, remova do índice local quaisquer
|
|
28
|
+
exclusões mais amplas de `.claude/` ou `.agents/` que o setup reportar e comite `base.json`,
|
|
29
|
+
as duas projeções de skills, hooks, instruções, pre-push e workflow. O marcador per-app
|
|
30
|
+
`opus.json` passa de `base/baseVersion` para `package/version`.
|
|
31
|
+
|
|
14
32
|
## 8.6.6 — 2026-07-30
|
|
15
33
|
|
|
16
34
|
**O pacote passa a ser publicado no npm PÚBLICO.** O registry próprio
|
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ await app.listen({ port: 3000 })
|
|
|
81
81
|
src/
|
|
82
82
|
core/ schema/ server/ client/ ui/
|
|
83
83
|
data/ auth/ audit/ log/ queue/ events/ scheduler/ dsl/
|
|
84
|
-
registry/ # scaffolds + skills
|
|
84
|
+
registry/ # scaffolds + skills Opus — geração e conhecimento do SDK, não runtime
|
|
85
85
|
bin/ # CLI (opus setup/gen/check/introspect/mcp) + libs
|
|
86
86
|
docs/
|
|
87
87
|
protocol.md # contrato completo (16 seções)
|
package/bin/cli.mjs
CHANGED
|
@@ -20,8 +20,9 @@ import { cmdGen, helpGen } from './lib/gen.mjs'
|
|
|
20
20
|
import { cmdDb } from './lib/db.mjs'
|
|
21
21
|
import { scanDir, emptyScanVerdict } from './lib/check.mjs'
|
|
22
22
|
import { createMonorepo, createProject } from './lib/create.mjs'
|
|
23
|
-
import { initProject, setupUiFoundation } from './lib/init.mjs'
|
|
23
|
+
import { initProject, repoRootOf, setupUiFoundation } from './lib/init.mjs'
|
|
24
24
|
import { introspect } from './lib/introspect.mjs'
|
|
25
|
+
import { materializeOpus } from './lib/materialize.mjs'
|
|
25
26
|
|
|
26
27
|
const __filename = fileURLToPath(import.meta.url)
|
|
27
28
|
const __dirname = path.dirname(__filename)
|
|
@@ -167,6 +168,12 @@ async function cmdAdd(name, options) {
|
|
|
167
168
|
async function cmdCheck(dir) {
|
|
168
169
|
const root = path.resolve(process.cwd(), dir ?? '.')
|
|
169
170
|
const rel = path.relative(process.cwd(), root) || '.'
|
|
171
|
+
const repoRoot = (await repoRootOf(root)) ?? root
|
|
172
|
+
const materialization = materializeOpus(repoRoot, 'check')
|
|
173
|
+
if (!materialization.ok) {
|
|
174
|
+
for (const error of materialization.errors) log('error', `✗ opus check: ${error}`)
|
|
175
|
+
process.exit(1)
|
|
176
|
+
}
|
|
170
177
|
const { files, actions, contracts, findings, hasMarker } = await scanDir(root)
|
|
171
178
|
|
|
172
179
|
if (actions === 0) {
|
|
@@ -202,6 +209,16 @@ async function cmdCheck(dir) {
|
|
|
202
209
|
process.exit(1)
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
async function cmdMaterializationCheck() {
|
|
213
|
+
const root = (await repoRootOf(process.cwd())) ?? process.cwd()
|
|
214
|
+
const result = materializeOpus(root, 'check')
|
|
215
|
+
if (!result.ok) {
|
|
216
|
+
for (const error of result.errors) log('error', `✗ opus check: ${error}`)
|
|
217
|
+
process.exit(1)
|
|
218
|
+
}
|
|
219
|
+
log('success', '✓ opus check: artefatos materializados estão atualizados.')
|
|
220
|
+
}
|
|
221
|
+
|
|
205
222
|
async function cmdCreate(dir, flags) {
|
|
206
223
|
if (!dir) {
|
|
207
224
|
log('error', 'Uso: opus create <dir> — o nome do diretório vira o nome do app (--monorepo: a raiz do workspace).')
|
|
@@ -224,6 +241,9 @@ async function cmdCreate(dir, flags) {
|
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
const r = await createProject(REGISTRY_DIR, target, name)
|
|
244
|
+
if (!r.setup.materialization.ok) {
|
|
245
|
+
throw new Error(`setup incompleto: ${r.setup.materialization.errors.join(' | ')}`)
|
|
246
|
+
}
|
|
227
247
|
const modeLabel = r.mode === 'app' ? 'app no monorepo (workspace detectado)' : 'repo standalone'
|
|
228
248
|
log('success', `✓ opus create: app "${name}" criado (base v${r.version}, ${modeLabel}).`)
|
|
229
249
|
console.log(`\n ${r.files.length} arquivos do esqueleto + setup (${r.setup.created.join(', ')}).`)
|
|
@@ -238,7 +258,7 @@ async function cmdCreate(dir, flags) {
|
|
|
238
258
|
pnpm install
|
|
239
259
|
pnpm --filter ${name} test && pnpm --filter ${name} manifest
|
|
240
260
|
|
|
241
|
-
|
|
261
|
+
Opcional: cadastre o app em uma interface de desenvolvimento como o Maestro.
|
|
242
262
|
`)
|
|
243
263
|
} else {
|
|
244
264
|
console.log(`
|
|
@@ -248,7 +268,7 @@ async function cmdCreate(dir, flags) {
|
|
|
248
268
|
pnpm install
|
|
249
269
|
pnpm test && pnpm exec opus check src && pnpm manifest
|
|
250
270
|
|
|
251
|
-
|
|
271
|
+
Opcional: cadastre o repo em uma interface de desenvolvimento como o Maestro.
|
|
252
272
|
`)
|
|
253
273
|
}
|
|
254
274
|
}
|
|
@@ -256,6 +276,10 @@ async function cmdCreate(dir, flags) {
|
|
|
256
276
|
async function cmdInit() {
|
|
257
277
|
const cwd = process.cwd()
|
|
258
278
|
const r = await initProject(REGISTRY_DIR, cwd)
|
|
279
|
+
if (!r.materialization.ok) {
|
|
280
|
+
for (const error of r.materialization.errors) log('error', `✗ opus setup: ${error}`)
|
|
281
|
+
process.exitCode = 1
|
|
282
|
+
}
|
|
259
283
|
log('success', `✓ opus setup: app v${r.version} ${r.wasInitialized ? 'atualizado' : 'inicializado'}.`)
|
|
260
284
|
if (r.created.length) {
|
|
261
285
|
console.log('\n criados (per-app):')
|
|
@@ -287,9 +311,8 @@ async function cmdInit() {
|
|
|
287
311
|
}
|
|
288
312
|
|
|
289
313
|
console.log(
|
|
290
|
-
'\n Per-app: opus.json
|
|
291
|
-
' A
|
|
292
|
-
' Gate de build: opus check.\n',
|
|
314
|
+
'\n Per-app: opus.json. No repo: base.json + skills/hooks/instruções comitados.\n' +
|
|
315
|
+
' A spec vive nas declarações (description → manifest). Gate: opus check.\n',
|
|
293
316
|
)
|
|
294
317
|
}
|
|
295
318
|
|
|
@@ -378,14 +401,15 @@ async function main() {
|
|
|
378
401
|
Comandos:
|
|
379
402
|
create <dir> Scaffolda um app opus-based canônico (vite+react+ui+domínio-exemplo);
|
|
380
403
|
detecta workspace (app em monorepo); --monorepo cria a RAIZ do workspace
|
|
381
|
-
setup
|
|
404
|
+
setup Reconcilia opus.json e os artefatos Opus versionados para Claude/Codex
|
|
382
405
|
add <name> Copia template do registry pro consumer
|
|
383
406
|
list Lista templates disponíveis
|
|
384
407
|
gen Gera manifest/openapi/docs/stubs a partir do opus.config.ts
|
|
385
408
|
check [dir] Valida as convenções das actions (régua de padrão; exit ≠ 0 se violar)
|
|
409
|
+
pre-push Gate Git: freshness dos artefatos + convenções das actions
|
|
386
410
|
db <verbo> Comandos de banco. v1: db check (drift-check entidade ↔ banco)
|
|
387
411
|
introspect [dir] Modelo da estrutura (actions/reactions/schedules + wiring); --json
|
|
388
|
-
mcp Server MCP (introspect/check/
|
|
412
|
+
mcp Server MCP (introspect/check/scaffold de action) — agentes via mcp_config
|
|
389
413
|
help Mostra esta mensagem
|
|
390
414
|
|
|
391
415
|
Flags:
|
|
@@ -411,8 +435,8 @@ Scaffolda um app opus-based CANÔNICO no diretório <dir> (recusa dir não-vazio
|
|
|
411
435
|
O esqueleto versiona com a base — nasce com os pré-requisitos plugados:
|
|
412
436
|
protocolo opus.config.ts + domínio-exemplo + manifest/scripts (+ teste verde)
|
|
413
437
|
UI vite + react + tailwind v4 CSS-first (tema + @source do Opus)
|
|
414
|
-
|
|
415
|
-
dia zero opus.json
|
|
438
|
+
desenvolvimento dev server na porta injetada (PORT) + allowedHosts do preview
|
|
439
|
+
dia zero opus.json + skills/hooks/instruções comitados no repo
|
|
416
440
|
|
|
417
441
|
Depois: git init && pnpm install && pnpm test — e cadastre repo/app no admin.
|
|
418
442
|
`)
|
|
@@ -427,16 +451,16 @@ Depois: git init && pnpm install && pnpm test — e cadastre repo/app no admin.
|
|
|
427
451
|
console.log(`
|
|
428
452
|
@softize/opus setup
|
|
429
453
|
|
|
430
|
-
|
|
431
|
-
opus.json marcador com a versão
|
|
432
|
-
|
|
454
|
+
Setup idempotente de um projeto Opus:
|
|
455
|
+
opus.json marcador per-app com a versão do SDK
|
|
456
|
+
base.json versão aplicada e eventuais skills excluídas
|
|
457
|
+
agentes skills Claude/Codex, hook, instruções, pre-push e CI comitados
|
|
433
458
|
|
|
434
459
|
Fundação de UI (só apps web — preset Tailwind + tema + flags do Opus):
|
|
435
460
|
tailwind.config criado se faltar; senão avisa pra estender o preset
|
|
436
461
|
tema/tsconfig/dep — avisa o que falta plugar (não edita seus arquivos: sem clobber)
|
|
437
462
|
|
|
438
|
-
|
|
439
|
-
NÃO pelo setup. Roda manual ou via postinstall.
|
|
463
|
+
O próprio pacote materializa somente seus artefatos. Base e Maestro são opcionais.
|
|
440
464
|
`)
|
|
441
465
|
return
|
|
442
466
|
}
|
|
@@ -500,6 +524,12 @@ passa verde). Default dir: cwd.
|
|
|
500
524
|
return
|
|
501
525
|
}
|
|
502
526
|
|
|
527
|
+
if (command === 'pre-push') {
|
|
528
|
+
if (rest[0] === 'materialization') await cmdMaterializationCheck()
|
|
529
|
+
else await cmdCheck(rest[0] ?? '.')
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
|
|
503
533
|
if (command === 'db') {
|
|
504
534
|
await cmdDb(rest, flags)
|
|
505
535
|
return
|
package/bin/lib/create.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (`registry/templates/app`). O esqueleto versiona com a base: sai do mesmo tarball que
|
|
4
4
|
* o SDK que ele configura, então nasce certo por construção — os pré-requisitos do
|
|
5
5
|
* protocolo (opus.config/manifest/scripts), da UI (preset+tema v4 CSS-first) e do
|
|
6
|
-
*
|
|
6
|
+
* previews externos (porta injetada, allowedHosts configurável) já vêm plugados.
|
|
7
7
|
*
|
|
8
8
|
* DOIS MODOS, por detecção (sobe do destino procurando `pnpm-workspace.yaml`):
|
|
9
9
|
* • standalone — repo próprio: o template inteiro (raiz inclusa);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* o npm pack descarta dotfiles do tarball, então a fonte não pode tê-los;
|
|
18
18
|
* • tokens `{{APP_NAME}}` e `{{OPUS_VERSION}}` são substituídos em todo arquivo.
|
|
19
19
|
*
|
|
20
|
-
* Depois da cópia roda o `initProject` (setup): opus.json
|
|
20
|
+
* Depois da cópia roda o `initProject` (setup): opus.json e artefatos Opus rastreáveis,
|
|
21
21
|
* `.claude/memory/` e o CI de fábrica (repo-level: acha a raiz pelo `.git`).
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -201,8 +201,8 @@ export async function createProject(registryDir, targetDir, appName) {
|
|
|
201
201
|
|
|
202
202
|
const warnings = mode === 'app' ? await workspaceWarnings(wsRoot, path.resolve(targetDir)) : []
|
|
203
203
|
|
|
204
|
-
// Setup por cima: opus.json
|
|
205
|
-
//
|
|
204
|
+
// Setup por cima: opus.json per-app e materialização repo-level. Em monorepo, a raiz
|
|
205
|
+
// é descoberta pelo Git e recebe skills, hooks, instruções e CI específicos do Opus.
|
|
206
206
|
const setup = await initProject(registryDir, targetDir)
|
|
207
|
-
return { version, mode, files, warnings, setup
|
|
207
|
+
return { version, mode, files, warnings, setup }
|
|
208
208
|
}
|
package/bin/lib/init.mjs
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* opus setup — faz um projeto "leigo" passar a CONHECER a base. PER-APP, idempotente:
|
|
3
|
-
* • grava o marcador `opus.json` (versão da base pinada) — fonte da verdade
|
|
4
|
-
* que torna o projeto auto-anunciável (Maestro/CI/agente leem isso);
|
|
5
|
-
* • cria `CLAUDE.md` SE faltar; se existir, atualiza SÓ o bloco gerenciado
|
|
6
|
-
* (`<!-- opus:base -->…<!-- /opus:base -->`) — fora dele o arquivo é do projeto
|
|
7
|
-
* e nunca é tocado; arquivo antigo sem marcadores fica 100% intocado.
|
|
8
|
-
*
|
|
9
|
-
* NÃO materializa a camada-base (`.claude`) — isso é REPO-LEVEL, do MAESTRO (pull do
|
|
10
|
-
* admin). Re-rodar atualiza pin + bloco. Roda manual (`opus setup`) ou via postinstall.
|
|
11
|
-
*/
|
|
1
|
+
/** opus setup: aplica o marcador per-app e os artefatos Opus rastreáveis no repo. */
|
|
12
2
|
|
|
13
3
|
import { promises as fs } from "node:fs";
|
|
14
4
|
import path from "node:path";
|
|
15
|
-
|
|
5
|
+
|
|
6
|
+
import { materializeOpus, PACKAGE_VERSION } from "./materialize.mjs";
|
|
16
7
|
|
|
17
8
|
const MARKER = "opus.json";
|
|
18
9
|
|
|
@@ -34,7 +25,7 @@ async function readJson(p) {
|
|
|
34
25
|
}
|
|
35
26
|
|
|
36
27
|
/** Raiz do repo (sobe até achar `.git`); sem repo → null (o chamador decide o fallback). */
|
|
37
|
-
async function repoRootOf(dir) {
|
|
28
|
+
export async function repoRootOf(dir) {
|
|
38
29
|
let cur = path.resolve(dir);
|
|
39
30
|
for (;;) {
|
|
40
31
|
if (await exists(path.join(cur, ".git"))) return cur;
|
|
@@ -44,86 +35,6 @@ async function repoRootOf(dir) {
|
|
|
44
35
|
}
|
|
45
36
|
}
|
|
46
37
|
|
|
47
|
-
/** CI de fábrica do projeto (só se faltar — depois é seu): roda o que existir em cada
|
|
48
|
-
* package via --if-present, então cresce junto com o projeto sem editar o workflow. */
|
|
49
|
-
function checkWorkflowTemplate() {
|
|
50
|
-
return `name: check
|
|
51
|
-
|
|
52
|
-
# Gate de PR/push (gerado pelo \`opus setup\` — edite à vontade, é seu).
|
|
53
|
-
# Roda o que existir em cada package: typecheck, test e manifest:check.
|
|
54
|
-
on:
|
|
55
|
-
push:
|
|
56
|
-
branches: [main]
|
|
57
|
-
pull_request: {}
|
|
58
|
-
workflow_dispatch: {}
|
|
59
|
-
|
|
60
|
-
jobs:
|
|
61
|
-
check:
|
|
62
|
-
runs-on: ubuntu-latest
|
|
63
|
-
steps:
|
|
64
|
-
- uses: actions/checkout@v4
|
|
65
|
-
- uses: pnpm/action-setup@v4
|
|
66
|
-
with:
|
|
67
|
-
version: 11
|
|
68
|
-
- uses: actions/setup-node@v4
|
|
69
|
-
with:
|
|
70
|
-
node-version: 22
|
|
71
|
-
cache: pnpm
|
|
72
|
-
- run: pnpm install --frozen-lockfile
|
|
73
|
-
- name: Typecheck
|
|
74
|
-
run: pnpm -r --if-present run typecheck
|
|
75
|
-
- name: Testes
|
|
76
|
-
run: pnpm -r --if-present run test
|
|
77
|
-
- name: Formatação
|
|
78
|
-
run: pnpm -r --if-present run format:check
|
|
79
|
-
# Manifest commitado tem que estar fresco (spec defasada = diff que mente).
|
|
80
|
-
- name: Manifest fresco
|
|
81
|
-
run: pnpm -r --if-present run manifest:check
|
|
82
|
-
`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const BASE_OPEN = "<!-- opus:base -->";
|
|
86
|
-
const BASE_CLOSE = "<!-- /opus:base -->";
|
|
87
|
-
const BASE_BLOCK_RE = /<!-- opus:base -->[\s\S]*?<!-- \/opus:base -->/;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Bloco GERENCIADO do CLAUDE.md — o miolo que o setup mantém em dia quando o Opus
|
|
91
|
-
* evolui (era only-if-missing: template melhorava e projeto antigo nunca via). Fora dos
|
|
92
|
-
* marcadores o arquivo é do projeto e o setup nunca toca. Sem número de versão no texto
|
|
93
|
-
* de propósito: a versão mora no `opus.json` (nada se duplica).
|
|
94
|
-
*/
|
|
95
|
-
export function claudeMdBaseBlock() {
|
|
96
|
-
return `${BASE_OPEN}
|
|
97
|
-
# Opus
|
|
98
|
-
|
|
99
|
-
Este projeto usa o **Opus** (\`@softize/opus\`), um SDK/protocolo de actions; a versão
|
|
100
|
-
fica pinada em \`opus.json\`.
|
|
101
|
-
|
|
102
|
-
## Antes de mexer
|
|
103
|
-
- **A camada \`.claude/\` é REPO-LEVEL, materializada — não editar à mão.** Skills, agents
|
|
104
|
-
e hooks vivem na raiz do repo, postos pelo **orquestrador da base** (o Opus semeia as
|
|
105
|
-
skills do framework; agents e método são o seu setup). Num monorepo o agente enxerga o
|
|
106
|
-
repo todo (libs compartilhadas inclusas).
|
|
107
|
-
- **Seu, deste app (per-app):** \`opus.json\` (pin), este \`CLAUDE.md\` (fora do bloco) e o código.
|
|
108
|
-
- **A spec vive nas DECLARAÇÕES:** cada action/entidade carrega o \`description\` (o doc
|
|
109
|
-
de negócio — a fonte); o \`opus gen\` projeta no manifest. Doc colado na declaração não
|
|
110
|
-
defasa (a \`domain.md\` à parte foi aposentada).
|
|
111
|
-
- **Gate de build:** \`opus check\` (valida as convenções das actions — defineAction e
|
|
112
|
-
o split defineContract+bindAction; exit ≠ 0 se violar OU se não achar action nenhuma).
|
|
113
|
-
Rode antes de commitar/buildar.
|
|
114
|
-
- **Atualizar o Opus:** depois do bump, leia \`node_modules/@softize/opus/CHANGELOG.md\`
|
|
115
|
-
(breaking = seção **Breaking**, com a migração) e rode os gates — eles apontam o
|
|
116
|
-
que a mudança cobra do código.
|
|
117
|
-
- **Estado/contratos ao vivo:** \`opus mcp\` (introspect/check/create-action).
|
|
118
|
-
- **Nada se duplica:** estado/contratos+negócio → declarações (\`description\`) → manifest ·
|
|
119
|
-
quem executa → agents · como se faz uma tarefa → skills. Regra duplicada em vez de
|
|
120
|
-
apontada é defeito — aponte.
|
|
121
|
-
|
|
122
|
-
> Bloco gerenciado pelo \`opus setup\` — acompanha o Opus. O que você escrever fora
|
|
123
|
-
> dele é seu; o setup nunca toca.
|
|
124
|
-
${BASE_CLOSE}`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
38
|
// =============================================================================
|
|
128
39
|
// Fundação de UI (apps web) — preset Tailwind + tema + flags
|
|
129
40
|
// =============================================================================
|
|
@@ -395,152 +306,34 @@ export async function setupUiFoundation(projectDir) {
|
|
|
395
306
|
return { applicable: true, created, warnings };
|
|
396
307
|
}
|
|
397
308
|
|
|
398
|
-
/**
|
|
399
|
-
* Inicializa/atualiza um projeto pra usar a base.
|
|
400
|
-
* @returns {Promise<{version:string, created:string[], synced:string[], warnings:string[], wasInitialized:boolean}>}
|
|
401
|
-
*/
|
|
402
|
-
/**
|
|
403
|
-
* Onde o `info/exclude` deste repo mora de verdade. `null` = não há onde escrever.
|
|
404
|
-
*
|
|
405
|
-
* Montar `<raiz>/.git/info/exclude` na mão parece óbvio e está errado em **worktree**,
|
|
406
|
-
* que é como toda sessão do Maestro nasce: ali `.git` não é pasta, é um ARQUIVO com um
|
|
407
|
-
* ponteiro (`gitdir: …`). Checar só existência passava, e o `mkdir` seguinte estourava
|
|
408
|
-
* `ENOTDIR` — derrubando o postinstall, e com ele o `pnpm install` inteiro. O sintoma
|
|
409
|
-
* chegava longe da causa: ambiente "subindo" pra sempre, sem erro na tela.
|
|
410
|
-
*
|
|
411
|
-
* Perguntar ao git responde certo nos dois layouts. O caminho montado à mão FICA como
|
|
412
|
-
* rede: `opus setup` roda em postinstall, e num ambiente sem `git` no PATH trocar a
|
|
413
|
-
* resposta por `null` faria a exclusão parar em silêncio — a base voltaria a sujar o
|
|
414
|
-
* `git status` de todo mundo. Só se aplica quando `.git` é pasta; sendo arquivo, sem o
|
|
415
|
-
* git não há como saber pra onde o ponteiro aponta, e chutar é o defeito original.
|
|
416
|
-
*/
|
|
417
|
-
function excludePathOf(repoRoot) {
|
|
418
|
-
try {
|
|
419
|
-
const p = execFileSync("git", ["rev-parse", "--git-path", "info/exclude"], {
|
|
420
|
-
cwd: repoRoot,
|
|
421
|
-
encoding: "utf-8",
|
|
422
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
423
|
-
}).trim();
|
|
424
|
-
// `--git-path` devolve relativo ao cwd quando o repo é o próprio cwd.
|
|
425
|
-
if (p !== "") return path.isAbsolute(p) ? p : path.join(repoRoot, p);
|
|
426
|
-
} catch {
|
|
427
|
-
/* sem git no PATH, ou fora de repo — cai na rede abaixo. */
|
|
428
|
-
}
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** A rede: só vale com `.git` PASTA, que é o layout onde montar o caminho acerta. */
|
|
433
|
-
async function excludePathFallback(repoRoot) {
|
|
434
|
-
const dotGit = path.join(repoRoot, ".git");
|
|
435
|
-
try {
|
|
436
|
-
if (!(await fs.stat(dotGit)).isDirectory()) return null;
|
|
437
|
-
} catch {
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
return path.join(dotGit, "info", "exclude");
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/** Anexa padrões ao info/exclude do repo (exclusão local, fora do .gitignore). */
|
|
444
|
-
async function gitExclude(repoRoot, patterns) {
|
|
445
|
-
const excludePath = excludePathOf(repoRoot) ?? (await excludePathFallback(repoRoot));
|
|
446
|
-
if (excludePath === null) return;
|
|
447
|
-
let cur = "";
|
|
448
|
-
try {
|
|
449
|
-
cur = await fs.readFile(excludePath, "utf-8");
|
|
450
|
-
} catch {
|
|
451
|
-
/* exclude novo. */
|
|
452
|
-
}
|
|
453
|
-
const have = new Set(cur.split("\n").map((l) => l.trim()));
|
|
454
|
-
const add = patterns.filter((p) => !have.has(p));
|
|
455
|
-
if (add.length === 0) return;
|
|
456
|
-
await fs.mkdir(path.dirname(excludePath), { recursive: true });
|
|
457
|
-
await fs.writeFile(
|
|
458
|
-
excludePath,
|
|
459
|
-
`${cur}${cur.endsWith("\n") || cur === "" ? "" : "\n"}${add.join("\n")}\n`,
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Semeia as skills DO PACOTE (registry/skills) em `.claude/skills/` do repo, sem
|
|
465
|
-
* clobber (o Maestro é o reconciliador quando o projeto for regido). Untracked por
|
|
466
|
-
* design: projeção da base, não código do projeto — vai pro info/exclude.
|
|
467
|
-
*/
|
|
468
|
-
async function seedRegistrySkills(registryDir, repoRoot) {
|
|
469
|
-
const from = path.join(registryDir, "skills");
|
|
470
|
-
if (!(await exists(from))) return 0;
|
|
471
|
-
let seeded = 0;
|
|
472
|
-
for (const slug of await fs.readdir(from)) {
|
|
473
|
-
const src = path.join(from, slug);
|
|
474
|
-
if (!(await fs.stat(src)).isDirectory()) continue;
|
|
475
|
-
const dest = path.join(repoRoot, ".claude", "skills", slug);
|
|
476
|
-
if (await exists(dest)) continue;
|
|
477
|
-
await fs.cp(src, dest, { recursive: true });
|
|
478
|
-
seeded += 1;
|
|
479
|
-
}
|
|
480
|
-
if (seeded > 0) await gitExclude(repoRoot, [".claude/skills/"]);
|
|
481
|
-
return seeded;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
309
|
export async function initProject(registryDir, projectDir) {
|
|
310
|
+
void registryDir;
|
|
485
311
|
const created = [];
|
|
486
312
|
const synced = [];
|
|
487
|
-
const
|
|
488
|
-
const version = pkg?.version ?? "0.0.0";
|
|
313
|
+
const version = PACKAGE_VERSION;
|
|
489
314
|
|
|
490
|
-
// Marcador (sempre regravado — é da base; pina a versão).
|
|
491
315
|
const markerPath = path.join(projectDir, MARKER);
|
|
492
316
|
const prev = await readJson(markerPath);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
317
|
+
const marker = `${JSON.stringify({ package: "@softize/opus", version }, null, 2)}\n`;
|
|
318
|
+
if (!(await exists(markerPath)) || (await fs.readFile(markerPath, "utf8")) !== marker) {
|
|
319
|
+
await fs.writeFile(markerPath, marker);
|
|
320
|
+
(prev === null ? created : synced).push(MARKER);
|
|
321
|
+
}
|
|
498
322
|
|
|
499
|
-
//
|
|
500
|
-
// do projeto). Arquivo antigo sem marcadores = 100% do projeto, intocado. A domain.md
|
|
501
|
-
// foi APOSENTADA (13/06/2026): a spec vive nas declarações (`description`).
|
|
323
|
+
// Remove somente o bloco legado que o próprio setup antigo declarava como gerenciado.
|
|
502
324
|
const claudePath = path.join(projectDir, "CLAUDE.md");
|
|
503
|
-
|
|
504
|
-
if (!(await exists(claudePath))) {
|
|
505
|
-
await fs.writeFile(claudePath, `${block}\n`);
|
|
506
|
-
created.push("CLAUDE.md");
|
|
507
|
-
} else {
|
|
325
|
+
if (await exists(claudePath)) {
|
|
508
326
|
const cur = await fs.readFile(claudePath, "utf-8");
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if (next !== cur) {
|
|
514
|
-
await fs.writeFile(claudePath, next);
|
|
515
|
-
synced.push("CLAUDE.md");
|
|
516
|
-
}
|
|
327
|
+
const next = cur.replace(/<!-- opus:base -->[\s\S]*?<!-- \/opus:base -->\s*/g, "");
|
|
328
|
+
if (next !== cur) {
|
|
329
|
+
await fs.writeFile(claudePath, next);
|
|
330
|
+
synced.push("CLAUDE.md (bloco legado removido)");
|
|
517
331
|
}
|
|
518
332
|
}
|
|
519
333
|
|
|
520
|
-
// Dia zero REPO-level — mas só do que é DO PROJETO (tracked, viaja no git):
|
|
521
|
-
// • semente da memória versionada (política da skill memory; sem o dir, o hook
|
|
522
|
-
// link-memory-on-start no-opa e a sessão roda amnésica);
|
|
523
|
-
// • CI de fábrica (check.yml) se faltar.
|
|
524
|
-
// A camada-base do ADMIN (agents + skills de metodologia) segue sendo do MAESTRO;
|
|
525
|
-
// as skills DO PACOTE (registry/skills) entram aqui: esqueleto standalone não pode
|
|
526
|
-
// nascer sem o que o próprio tarball carrega (projeção untracked, git-excluded —
|
|
527
|
-
// o Maestro re-materializa por cima quando reger o projeto).
|
|
528
334
|
const repoRoot = (await repoRootOf(projectDir)) ?? projectDir;
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
created.push(`.claude/skills/ (${seededSkills} da base, untracked)`);
|
|
532
|
-
const memDir = path.join(repoRoot, ".claude", "memory");
|
|
533
|
-
if (!(await exists(memDir))) {
|
|
534
|
-
await fs.mkdir(memDir, { recursive: true });
|
|
535
|
-
await fs.writeFile(path.join(memDir, "MEMORY.md"), "");
|
|
536
|
-
created.push(".claude/memory/ (repo)");
|
|
537
|
-
}
|
|
538
|
-
const ciPath = path.join(repoRoot, ".github", "workflows", "check.yml");
|
|
539
|
-
if (!(await exists(ciPath))) {
|
|
540
|
-
await fs.mkdir(path.dirname(ciPath), { recursive: true });
|
|
541
|
-
await fs.writeFile(ciPath, checkWorkflowTemplate());
|
|
542
|
-
created.push(".github/workflows/check.yml (repo)");
|
|
543
|
-
}
|
|
335
|
+
const materialization = materializeOpus(repoRoot, "setup");
|
|
336
|
+
synced.push(...materialization.changes);
|
|
544
337
|
|
|
545
338
|
// Avisos de dia zero (sem clobber — igual à fundação de UI): o que falta plugar à mão.
|
|
546
339
|
const warnings = [];
|
|
@@ -551,6 +344,5 @@ export async function initProject(registryDir, projectDir) {
|
|
|
551
344
|
);
|
|
552
345
|
}
|
|
553
346
|
|
|
554
|
-
|
|
555
|
-
return { version, created, synced, warnings, wasInitialized: prev !== null };
|
|
347
|
+
return { version, created, synced, warnings, wasInitialized: prev !== null, materialization };
|
|
556
348
|
}
|