@aksp/opencrew 1.4.2 → 1.6.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 (32) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +23 -11
  3. package/package.json +1 -1
  4. package/src/commands/init.js +20 -16
  5. package/src/commands/update.js +58 -42
  6. package/src/lib/ides.js +15 -12
  7. package/src/lib/manifest.js +89 -0
  8. package/src/lib/migrations.js +110 -0
  9. package/templates/.mcp.json +1 -1
  10. package/templates/AGENTS.md +2 -1
  11. package/templates/_opencrew/.opencrew-version +1 -1
  12. package/templates/_opencrew/core/best-practices/copywriting.md +4 -1
  13. package/templates/_opencrew/core/best-practices/image-design.md +5 -5
  14. package/templates/_opencrew/core/best-practices/instagram-feed.md +4 -4
  15. package/templates/_opencrew/core/best-practices/instagram-reels.md +1 -1
  16. package/templates/_opencrew/core/best-practices/review.md +7 -0
  17. package/templates/_opencrew/core/best-practices/social-networks-publishing.md +8 -8
  18. package/templates/_opencrew/core/prompts/build.prompt.md +16 -0
  19. package/templates/_opencrew/core/prompts/discovery.prompt.md +15 -2
  20. package/templates/_opencrew/core/runner.pipeline.md +86 -11
  21. package/templates/_opencrew/core/scripts/conferir-fontes.mjs +189 -0
  22. package/templates/_opencrew/core/scripts/verificar/leitura.mjs +104 -0
  23. package/templates/_opencrew/core/scripts/verificar/regras.mjs +127 -0
  24. package/templates/_opencrew/core/scripts/verificar.mjs +118 -0
  25. package/templates/skills/image-ai-generator/SKILL.md +1 -1
  26. package/templates/skills/image-creator/SKILL.md +2 -2
  27. package/templates/skills/image-fetcher/SKILL.md +1 -1
  28. package/templates/skills/opencrew-best-practice-creator/SKILL.md +4 -4
  29. package/templates/skills/template-designer/SKILL.md +3 -3
  30. package/templates/skills/template-designer/base-templates/model-a.html +1 -1
  31. package/templates/skills/template-designer/base-templates/model-b.html +1 -1
  32. package/templates/skills/template-designer/base-templates/model-c.html +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,69 @@
3
3
  All notable changes to opencrew are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
+ ## [1.6.0] — 2026-10-02
7
+
8
+ Trilha U2 "Crew que conhece o projeto" + U6 "Convivência" (`specs/fase-u2-crew-que-conhece-o-projeto.md`).
9
+
10
+ ### Added
11
+ - **`fontes:` no `crew.yaml`** — arquivos/pastas do projeto (caminho relativo) que a crew lê em todo
12
+ run e trata como verdade; o discovery pergunta quais são.
13
+ - **Conferência de fontes** (`_opencrew/core/scripts/conferir-fontes.mjs`) no início de cada run:
14
+ arquivo movido → acha o novo lugar e oferece corrigir (`--corrigir`, com `.bak`); nome diferente →
15
+ lista a pasta; caminho absoluto → alerta "não é portátil". No uso real (Projeto B) achou os 5
16
+ caminhos quebrados pela reorganização, cada um com o lugar exato.
17
+ - **Correção gravada na hora** — o que o usuário corrige num checkpoint vai para a memória antes do
18
+ próximo passo; termo removido vira proibição entre aspas (trava do verificador); conflito com o
19
+ `company.md` gera a pergunta "Atualizo o perfil da empresa?".
20
+ - **`_opencrew/best-practices.local/`** — best-practices do usuário (aprendidas/criadas), lidas antes
21
+ das do core e nunca tocadas pelo `update`; o verificador também lê os limites dali primeiro.
22
+
23
+ ### Changed
24
+ - **`update` completo e seguro**: entrega pastas novas do framework (agentes-base, config, templates
25
+ de crew) sem sobrescrever; guarda em `.opencrew-backup/<data>/` o que o usuário editou antes de
26
+ substituir (manifesto `_opencrew/manifest.json`); recusa voltar para versão mais antiga; atualiza
27
+ as pontes **só das IDEs instaladas**; faz merge do Playwright no `.mcp.json` (saída em
28
+ `_opencrew/logs/playwright/`, outros servidores intactos); avisa sobre pontes antigas (`opensquad`).
29
+ - **Convivência**: pontes e bloco do `AGENTS.md` só ativam o OpenCrew com `/opencrew` (ou pedido
30
+ sobre crews) e apontam direto para `_opencrew/core/system.md`; outras instruções do projeto têm
31
+ prioridade no resto.
32
+ - Migração do formato de memória faz `memories.md.bak` e avisa (fim do reset silencioso); regra única
33
+ sobre o que vai para a memória (só feedback explícito).
34
+ - `_build/discovery.yaml` agora em `crews/{code}/_build/`; build grava caminhos relativos à raiz.
35
+ ## [1.5.0] — 2026-10-02
36
+
37
+ Trilha U1 "Revisor com dentes" — primeira melhoria vinda do uso real
38
+ (`docs/jornada/2026-10-02-uso-real.md`, `specs/fase-u1-revisor-com-dentes.md`).
39
+
40
+ ### Added
41
+ - **Verificador automático** (`_opencrew/core/scripts/verificar.mjs`, Node puro): mede o texto
42
+ ANTES do revisor usando os limites `constraints:` dos best-practices — título e meta description
43
+ do blog, legenda e hashtags do Instagram, slides do carrossel, post do LinkedIn, tweets, links
44
+ (alerta quando abaixo do mínimo). Bloqueia placeholders (`wa.me/55…9999…`, `[Empresa X]`,
45
+ `lorem ipsum`…), termos entre aspas em `## Proibições Explícitas` da memória da crew e
46
+ `[PREENCHER: …]`; alerta afirmações em 1ª pessoa com dado concreto (R$, %, ano passado,
47
+ "N clientes"). Relatório em PT-BR com valor medido × limite; última linha
48
+ `VERIFICACAO:OK | BLOQUEADA | AGUARDANDO_USUARIO`.
49
+ - **Regras de veracidade** injetadas em todo passo de criação: nunca inventar casos, depoimentos,
50
+ números ou histórias em 1ª pessoa — usar `[PREENCHER: o que falta]`.
51
+
52
+ ### Changed
53
+ - **Revisão com trava**: antes de todo passo com `on_reject`, o runner verifica **todas** as saídas
54
+ desde o redator (não só a entrada do revisor — no uso real as legendas nunca eram revisadas);
55
+ `VERIFICACAO:BLOQUEADA` força REJECT seja qual for a nota; no limite de ciclos o usuário escolhe
56
+ corrigir, aceitar (registrado) ou abortar; a aprovação final mostra o resumo e pede os
57
+ `[PREENCHER]`.
58
+ - `review.md`: o revisor copia os números do relatório (nunca estima), não aprova com bloqueio e
59
+ tem nota máxima 7/10 com alerta não resolvido. `copywriting.md` e o build: regra de não inventar.
60
+ - **Instagram em 4:5**: carrossel/feed agora 1080×1350 (a API do Instagram só publica de 4:5 a
61
+ 1,91:1), no máximo 10 slides; presets do `image-creator`, `template-designer` (e modelos-base),
62
+ `image-fetcher` e best-practices atualizados. Limites com nomes canônicos (`hashtags_max`).
63
+
64
+ ### Internal
65
+ - Docs de jornada (`docs/jornada/`: uso real, roteiro de teste U0, medições) e roadmap de trilhas U.
66
+ - Regras de dev 12 (limite só vale se medido) e 13 (PT-BR para o usuário); alerta de tamanho e
67
+ lint cobrem os scripts do runtime.
68
+
6
69
  ## [1.4.2] — 2026-10-02
7
70
 
8
71
  Hotfix "parar de causar dano" (Fase 1 da auditoria — `specs/fase-1-hotfix.md`).
package/README.md CHANGED
@@ -35,6 +35,15 @@ dentro da sua IDE.**
35
35
  - 🎛️ **Seleção inteligente de agentes** — o sistema analisa seu pedido e
36
36
  sugere quais agentes são necessários para aquela tarefa. Você confirma ou
37
37
  ajusta com um clique. Agentes pulados não gastam tokens naquele run.
38
+ - 🔎 **Revisor com dentes** — antes da revisão, um verificador automático mede o texto
39
+ (tamanho de título, meta description, legenda, hashtags, slides), barra placeholders,
40
+ termos que você proibiu e `[PREENCHER]` pendentes, e aponta afirmações a confirmar.
41
+ Bloqueio não passa, seja qual for a nota do revisor. A crew não inventa casos nem números:
42
+ quando falta um dado real, ela pergunta na aprovação final.
43
+ - 📂 **Crew que conhece o projeto** — liste em `fontes:` os arquivos e pastas do seu projeto
44
+ (decisões, calendário, manual de marca) e a crew os lê em todo run, tratando-os como verdade.
45
+ Reorganizou as pastas? No início do run ela confere os caminhos, acha para onde o arquivo foi
46
+ e oferece corrigir. Correções que você faz num checkpoint ficam gravadas na hora.
38
47
 
39
48
  ---
40
49
 
@@ -196,28 +205,31 @@ meu-projeto/
196
205
  ## Mantendo o OpenCrew atualizado
197
206
 
198
207
  ```bash
199
- npx @aksp/opencrew update
208
+ npx @aksp/opencrew@latest update
200
209
  ```
201
210
 
202
- O `update` não toca nas suas crews nem na sua memória. Ele atualiza apenas:
211
+ Um único comando traz **todas** as melhorias para quem já usa uma versão antiga — sem perder
212
+ o que você fez:
203
213
 
204
214
  | O que é atualizado | O que NUNCA é tocado |
205
215
  |---|---|
206
- | `_opencrew/core/` (framework — sobrescrito por inteiro) | `crews/` (suas crews) |
207
- | Skills do catálogo (sobrescritas — edições locais se perdem) | `_opencrew/_memory/` (perfil, preferências) |
208
- | `_opencrew/core/system.md` | `.env` (suas chaves) |
209
- | Bloco `<!-- opencrew -->` no `AGENTS.md` | Pontes das IDEs (`CLAUDE.md`, `.cursor/`, `.agents/`…) |
216
+ | `_opencrew/core/` (framework) e skills do catálogo | `crews/` (suas crews) |
217
+ | Pastas novas do framework (agentes-base, config) — só o que falta | `_opencrew/_memory/` (perfil, preferências) |
218
+ | Pontes das IDEs **que você já tem instaladas** (nunca cria de IDE nova) | `_opencrew/best-practices.local/` (suas best-practices) |
219
+ | Bloco `<!-- opencrew -->` do `AGENTS.md`/`CLAUDE.md` (o resto do arquivo fica intacto) | `.env` (suas chaves) |
220
+ | Servidor Playwright no `.mcp.json` (outros servidores intactos) | |
210
221
 
211
- As **pontes das IDEs não são atualizadas** pelo `update`. Para regravá-las com a versão
212
- atual, use:
222
+ - **Editou um arquivo do framework ou um skill do catálogo?** Antes de substituir, o `update`
223
+ guarda a sua versão em `.opencrew-backup/<data>/` e lista o que copiou.
224
+ - **Versão mais nova instalada?** O `update` não volta para uma versão mais antiga (cache do
225
+ `npx`): ele para e pede `npx @aksp/opencrew@latest update`.
226
+
227
+ Para regravar as pontes de IDEs específicas (ou adicionar uma IDE nova):
213
228
 
214
229
  ```bash
215
230
  npx @aksp/opencrew init --repair-bridges --ide=claude-code
216
231
  ```
217
232
 
218
- (troque `claude-code` pelas IDEs que você usa, separadas por vírgula; sem `--ide`, o
219
- comando grava as pontes de **todas** as IDEs suportadas).
220
-
221
233
  Se você está migrando de uma versão anterior a v1.3, o `update` detecta
222
234
  AGENTS.md legados (sistema completo de 150 linhas) e os substitui pela ponte
223
235
  fina. Desde a v1.4.2 o arquivo original é copiado antes para `AGENTS.md.bak` (até a v1.4.1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aksp/opencrew",
3
- "version": "1.4.2",
3
+ "version": "1.6.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,7 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { promises as fs } from 'node:fs';
3
3
  import { templatesDir, packageJsonPath } from '../lib/paths.js';
4
- import { copyDir, exists, writeFileSafe, readJson, writeBridgeFile } from '../lib/fsx.js';
4
+ import { exists, writeFileSafe, readJson, writeBridgeFile } from '../lib/fsx.js';
5
+ import { newDelivery, deliverTree, deliverFile, writeManifest, readManifest } from '../lib/manifest.js';
5
6
  import { ideById, allIdeIds, AGENTS_BRIDGE } from '../lib/ides.js';
6
7
  import { pickIdes as promptIdes } from '../lib/prompts.js';
7
8
  import { UsageError } from '../lib/errors.js';
@@ -26,7 +27,10 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
26
27
  const ids = await resolveIdes(opts, async () => allIdeIds());
27
28
  log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — repairing IDE bridges`);
28
29
  log(c.dim(`Target: ${target}\n`));
29
- await writeBridges(target, ids, { overwrite: true });
30
+ const previous = await readManifest(target);
31
+ const repairCtx = newDelivery(target, previous);
32
+ await writeBridges(target, ids, { overwrite: true, ctx: repairCtx });
33
+ await writeManifest(target, version, { ...(previous?.files ?? {}), ...repairCtx.files });
30
34
 
31
35
  log(`\n${c.green(c.bold('Done!'))} IDE bridges regenerated.\n`);
32
36
  log(`${c.bold('Next step:')} Restart your IDE, then type ${c.cyan('/opencrew')} to verify.\n`);
@@ -51,7 +55,8 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
51
55
 
52
56
  // 1. Copy the framework payload (never clobber user work).
53
57
  step('Installing framework files');
54
- const copied = await installPayload(target);
58
+ const ctx = newDelivery(target, null);
59
+ const copied = await installPayload(target, ctx);
55
60
  ok(`Framework files ready (${copied} written, existing files preserved)`);
56
61
 
57
62
  // 2. System doc + root configs.
@@ -59,7 +64,7 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
59
64
 
60
65
  // Full system definition lives in _opencrew/core/ — never at project root.
61
66
  // The root AGENTS.md is just a thin bridge (like CLAUDE.md, GEMINI.md, etc.).
62
- await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), await tpl('AGENTS.md'));
67
+ await deliverFile(ctx, path.join(target, '_opencrew', 'core', 'system.md'), await tpl('AGENTS.md'), { overwrite: true });
63
68
  ok('_opencrew/core/system.md (full system definition)');
64
69
 
65
70
  const agentsResult = await writeBridgeFile(path.join(target, 'AGENTS.md'), AGENTS_BRIDGE);
@@ -78,14 +83,16 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
78
83
 
79
84
  // 3. IDE bridge files.
80
85
  step('Configuring AI IDEs');
81
- await writeBridges(target, ids, { overwrite: false });
86
+ await writeBridges(target, ids, { overwrite: false, ctx });
82
87
 
83
88
  if (ids.includes('claude-code')) {
84
89
  warn(`opencrew ships its own Playwright MCP server (.mcp.json) — disable Claude Code's`);
85
90
  warn(`native Playwright plugin/extension to avoid the two conflicting.`);
86
91
  }
87
92
 
88
- // 4. Version stamp — written LAST: it is what marks the install as complete.
93
+ // 4. Manifest (what OpenCrew delivered — lets `update` spot the user's edits), then the
94
+ // version stamp LAST: it is what marks the install as complete.
95
+ await writeManifest(target, version, ctx.files);
89
96
  await fs.writeFile(path.join(target, STAMP), version + '\n');
90
97
 
91
98
  // 5. Done.
@@ -101,21 +108,18 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
101
108
  * Never copies the version stamp: only a finished init writes it.
102
109
  * @returns {Promise<number>} files written
103
110
  */
104
- export async function installPayload(target) {
105
- let count = 0;
106
- const onCopy = () => (count += 1);
107
- await copyDir(path.join(templatesDir, '_opencrew'), path.join(target, '_opencrew'), {
111
+ export async function installPayload(target, ctx = newDelivery(target, null)) {
112
+ await deliverTree(ctx, path.join(templatesDir, '_opencrew'), path.join(target, '_opencrew'), {
108
113
  overwrite: false,
109
114
  // Never ship stray logs, browser sessions or the template's own stamp.
110
115
  skip: (rel) =>
111
116
  (rel.startsWith('logs/') && rel !== 'logs/.gitkeep') ||
112
117
  rel.startsWith('_browser_profile/') ||
113
118
  rel === '.opencrew-version',
114
- onCopy,
115
119
  });
116
- await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), { overwrite: false, onCopy });
117
- await copyDir(path.join(templatesDir, 'crews'), path.join(target, 'crews'), { overwrite: false });
118
- return count;
120
+ await deliverTree(ctx, path.join(templatesDir, 'skills'), path.join(target, 'skills'), { overwrite: false });
121
+ await deliverTree(ctx, path.join(templatesDir, 'crews'), path.join(target, 'crews'), { overwrite: false });
122
+ return ctx.written;
119
123
  }
120
124
 
121
125
  /** 'none' (no core) · 'partial' (core without stamp: interrupted install) · 'complete'. */
@@ -148,7 +152,7 @@ async function resolveIdes(opts, fallback) {
148
152
  * @param {string[]} ids — validated IDE ids to configure
149
153
  * @param {{ overwrite: boolean }} opts
150
154
  */
151
- async function writeBridges(target, ids, { overwrite }) {
155
+ async function writeBridges(target, ids, { overwrite, ctx }) {
152
156
  const writtenPaths = new Set();
153
157
 
154
158
  for (const id of ids) {
@@ -162,7 +166,7 @@ async function writeBridges(target, ids, { overwrite }) {
162
166
  const fp = path.join(target, f.path);
163
167
  const hasFrontmatter = f.content.startsWith('---');
164
168
  if (hasFrontmatter) {
165
- await writeFileSafe(fp, f.content, { overwrite });
169
+ await deliverFile(ctx, fp, f.content, { overwrite });
166
170
  } else {
167
171
  const result = await writeBridgeFile(fp, f.content);
168
172
  if (result.merged) info(`${f.path} (merged — existing content preserved)`);
@@ -1,17 +1,19 @@
1
1
  import path from 'node:path';
2
2
  import { promises as fs } from 'node:fs';
3
3
  import { templatesDir, packageJsonPath } from '../lib/paths.js';
4
- import { copyDir, exists, writeFileSafe, readJson, writeBridgeFile, readFile } from '../lib/fsx.js';
4
+ import { exists, writeFileSafe, readJson, writeBridgeFile, readFile } from '../lib/fsx.js';
5
5
  import { AGENTS_BRIDGE, LEAKED_STATUS_SECTION, ideById } from '../lib/ides.js';
6
- import { c, log, info, ok, warn, step } from '../lib/ui.js';
7
-
8
- // Update refreshes ONLY the framework. It never touches:
9
- // crews/, _opencrew/_memory/, _opencrew/_browser_profile/, .env, IDE bridges
10
- // (one exception: the CLAUDE.md block leaked by 1.4.0/1.4.1 — see removeLeakedStatusSection).
11
- // Note: catalog skills (skills/<name>/ that ship with the package) ARE fully
12
- // overwritten below — user edits to a catalog skill's own files are not preserved.
13
- // Only skill directories that don't exist in the package's templates/skills/ at all
14
- // (i.e. custom/user-authored skills) are left untouched.
6
+ import { readManifest, writeManifest, newDelivery, deliverTree, deliverFile } from '../lib/manifest.js';
7
+ import { compareVersions, detectInstalledIdes, refreshBridges, mergeMcp, findLegacyBridges } from '../lib/migrations.js';
8
+ import { c, log, info, ok, warn, err, step } from '../lib/ui.js';
9
+
10
+ // `update` brings EVERY improvement to people who already use OpenCrew (AGENTS.md rule 14),
11
+ // without losing what they made:
12
+ // - _opencrew/core and catalog skills are replaced — a file the user edited is copied to
13
+ // .opencrew-backup/<date>/ first (manifest of hashes; none = copy whatever differs);
14
+ // - new framework folders (agents, config, crew templates) arrive without overwriting;
15
+ // - bridges of the IDEs already installed are refreshed (never new IDEs);
16
+ // - crews/, _opencrew/_memory/, _opencrew/best-practices.local/ and .env are never touched.
15
17
  export async function update(opts = {}) {
16
18
  const target = process.cwd();
17
19
  const pkg = await readJson(packageJsonPath);
@@ -27,14 +29,15 @@ export async function update(opts = {}) {
27
29
  const current = (await exists(versionFile))
28
30
  ? (await fs.readFile(versionFile, 'utf8')).trim()
29
31
  : 'unknown';
32
+ const newer = current !== 'unknown' && compareVersions(current, version) > 0;
30
33
 
31
34
  log(`\n${c.bold(c.cyan('opencrew update'))}`);
32
35
  log(c.dim(`Installed: ${current} → Package: ${version}\n`));
33
36
 
34
37
  if (opts.check) {
35
- if (current === version) {
36
- ok(`Up to date (v${version}).`);
37
- } else {
38
+ if (current === version) ok(`Up to date (v${version}).`);
39
+ else if (newer) info(`A versão instalada (v${current}) é mais nova que este pacote (v${version}).`);
40
+ else {
38
41
  info(`Update available: v${current} → v${version}.`);
39
42
  info(`Run ${c.cyan('npx @aksp/opencrew update')} to apply.`);
40
43
  process.exitCode = 1;
@@ -42,45 +45,58 @@ export async function update(opts = {}) {
42
45
  return;
43
46
  }
44
47
 
45
- if (current === version) {
46
- ok('Already up to date. Refreshing framework files anyway.');
48
+ if (newer) {
49
+ err(`Você tem a v${current} instalada e este pacote é a v${version} (mais antigo). Nada foi alterado.`);
50
+ info(`Use ${c.cyan('npx @aksp/opencrew@latest update')}.`);
51
+ process.exitCode = 1;
52
+ return;
47
53
  }
48
54
 
49
- // Refresh core framework: _opencrew/core is fully overwritten (it is not user data).
55
+ const manifest = await readManifest(target);
56
+ const ctx = newDelivery(target, manifest);
57
+ const tpl = (...p) => path.join(templatesDir, ...p);
58
+ const dest = (...p) => path.join(target, ...p);
59
+
50
60
  step('Refreshing framework');
51
- let n = 0;
52
- await copyDir(path.join(templatesDir, '_opencrew', 'core'), path.join(target, '_opencrew', 'core'), {
53
- overwrite: true,
54
- onCopy: () => (n += 1),
55
- });
56
- ok(`_opencrew/core refreshed (${n} files)`);
57
-
58
- // Refresh catalog skills: every skill shipped in templates/skills/ is fully
59
- // overwritten (edits to a catalog skill's files do not survive an update).
60
- // Skill directories that only exist in the user's project — i.e. not part of
61
- // the catalog — are never touched, since copyDir only visits paths that exist
62
- // in the source (templates/skills/).
63
- step('Refreshing catalog skills');
64
- warn('Catalog skills are fully overwritten — your edits to any built-in skill files will be lost.');
65
- let s = 0;
66
- await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
67
- overwrite: true,
68
- onCopy: () => (s += 1),
69
- });
70
- ok(`Catalog skills refreshed (${s} files)`);
71
-
72
- // System doc — full definition in _opencrew/core/, thin bridge at root.
73
- const systemContent = await fs.readFile(path.join(templatesDir, 'AGENTS.md'), 'utf8');
74
- await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), systemContent);
75
- ok('_opencrew/core/system.md refreshed');
61
+ await deliverTree(ctx, tpl('_opencrew', 'core'), dest('_opencrew', 'core'), { overwrite: true });
62
+ await deliverFile(ctx, dest('_opencrew', 'core', 'system.md'), await fs.readFile(tpl('AGENTS.md')), { overwrite: true });
63
+ await deliverTree(ctx, tpl('skills'), dest('skills'), { overwrite: true });
64
+ // New framework folders (e.g. base agents since 1.3.2): only what is missing.
65
+ for (const dir of ['agents', 'config', '_investigations']) {
66
+ await deliverTree(ctx, tpl('_opencrew', dir), dest('_opencrew', dir), { overwrite: false });
67
+ }
68
+ await deliverTree(ctx, tpl('crews'), dest('crews'), { overwrite: false });
69
+ ok(`Framework and catalog skills refreshed (${ctx.written} files written)`);
76
70
 
71
+ step('Refreshing IDE bridges');
77
72
  await refreshAgentsBridge(target);
73
+ const ides = await detectInstalledIdes(target);
74
+ await refreshBridges(ctx, ides);
75
+ ok(ides.length ? `Bridges refreshed: ${ides.map((i) => i.label).join(', ')}` : 'No IDE bridges found to refresh');
78
76
  await removeLeakedStatusSection(target);
79
77
 
78
+ const mcp = await mergeMcp(target, tpl('.mcp.json'));
79
+ if (mcp === 'updated' || mcp === 'created') ok(`.mcp.json (Playwright: ${mcp === 'created' ? 'created' : 'saída em _opencrew/logs/playwright/'})`);
80
+ if (mcp === 'invalid') warn('.mcp.json não é um JSON válido — não alterado. Confira o arquivo.');
81
+
82
+ for (const legacy of await findLegacyBridges(target)) {
83
+ warn(`Ponte antiga encontrada: ${legacy} (aponta para _opensquad/, que não existe neste projeto). Pode apagar com segurança.`);
84
+ }
85
+
86
+ if (ctx.copied.length) {
87
+ const rel = path.relative(target, ctx.backupDir).split(path.sep).join('/');
88
+ const what = manifest ? 'que você tinha editado' : 'diferentes do pacote novo';
89
+ warn(`${ctx.copied.length} arquivo(s) ${what} foram copiados para ${rel}/ antes de serem substituídos:`);
90
+ for (const f of ctx.copied.slice(0, 15)) log(` ${f}`);
91
+ if (ctx.copied.length > 15) log(` … e mais ${ctx.copied.length - 15}`);
92
+ if (!manifest) info('Primeira atualização com proteção: sem registro anterior, guardamos tudo o que diferia. Daqui em diante, só o que você editar.');
93
+ }
94
+
95
+ await writeManifest(target, version, ctx.files);
80
96
  // Stamp last: a crash above leaves the old version, so the next update retries.
81
97
  await fs.writeFile(versionFile, version + '\n');
82
98
  log(`\n${c.green(c.bold('Updated to v' + version))}.`);
83
- log(c.dim('Your crews, memory, IDE bridges and .env were left untouched.\n'));
99
+ log(c.dim('Your crews, memory, local best-practices and .env were left untouched.\n'));
84
100
  }
85
101
 
86
102
  // Root AGENTS.md: create it if missing; a legacy full-system doc (pre-v1.3) is backed up
package/src/lib/ides.js CHANGED
@@ -1,12 +1,18 @@
1
- // Single source of truth = AGENTS.md (shipped at project root).
2
- // Every IDE gets only a THIN bridge file that points at AGENTS.md.
1
+ // Single source of truth = _opencrew/core/system.md (from templates/AGENTS.md).
2
+ // Every IDE gets only a THIN bridge file that points at it.
3
3
  // Adding support for a new IDE = one more entry in this list.
4
4
 
5
- const BRIDGE = `Read \`AGENTS.md\` at the project root and adopt the opencrew system role.
6
- Follow all initialization, command routing, and workflow instructions defined there.
5
+ // Coexistence (U6): opencrew only takes over when called — other agent systems in the same
6
+ // project keep priority for everything else.
7
+ const ACTIVATION = `Use opencrew ONLY when the user types \`/opencrew\` or asks to create, run or manage
8
+ AI agent crews. In that case, read \`_opencrew/core/system.md\` and follow its initialization,
9
+ command routing and workflow instructions. For anything else, the other instructions of this
10
+ project take precedence.`;
11
+
12
+ const BRIDGE = `${ACTIVATION}
7
13
 
8
14
  If invoked with arguments (e.g. \`/opencrew create ...\`, \`/opencrew run ...\`),
9
- route to the matching action from the Command Routing table in AGENTS.md.
15
+ route to the matching action from the Command Routing table in \`_opencrew/core/system.md\`.
10
16
  If invoked without arguments, show the Main Menu.`;
11
17
 
12
18
  // Claude Code needs one extra rule (checkpoints must use AskUserQuestion) and a
@@ -20,7 +26,7 @@ description: "opencrew — multi-agent orchestration. Use when the user types /o
20
26
 
21
27
  ${BRIDGE}
22
28
 
23
- ## Claude Code specifics (override AGENTS.md where they conflict)
29
+ ## Claude Code specifics (override system.md where they conflict)
24
30
 
25
31
  - **Checkpoints MUST use \`AskUserQuestion\`** — never output a checkpoint question as plain text.
26
32
  Combine multiple questions into a single call (max 4 slots, each with 2–4 options).
@@ -32,7 +38,8 @@ ${BRIDGE}
32
38
  const CLAUDE_MD = `# opencrew — Project Instructions
33
39
 
34
40
  This project uses **opencrew**, a multi-agent orchestration framework.
35
- The full system definition lives in \`AGENTS.md\` — read it and adopt that role.
41
+
42
+ ${ACTIVATION}
36
43
 
37
44
  Type \`/opencrew\` to open the main menu.
38
45
 
@@ -44,11 +51,7 @@ Type \`/opencrew\` to open the main menu.
44
51
  `;
45
52
 
46
53
  // Root AGENTS.md: thin bridge to the full system definition (written by init and update).
47
- export const AGENTS_BRIDGE = '# opencrew\n\n'
48
- + 'The opencrew system definition lives at `_opencrew/core/system.md`.\n'
49
- + 'Read that file and adopt the opencrew system role — follow all initialization,\n'
50
- + 'command routing, and workflow instructions defined there.\n\n'
51
- + 'Type `/opencrew` to open the main menu.\n';
54
+ export const AGENTS_BRIDGE = `# opencrew\n\n${ACTIVATION}\n\nType \`/opencrew\` to open the main menu.\n`;
52
55
 
53
56
  // Marker that identifies the maintainer STATUS.md section leaked into CLAUDE.md by 1.4.0/1.4.1.
54
57
  export const LEAKED_STATUS_SECTION = '## STATUS.md (gestão de sessão)';
@@ -0,0 +1,89 @@
1
+ // Manifest of the files OpenCrew delivered (path → sha256). It lets `update` tell a file the
2
+ // user edited (copy it to .opencrew-backup/ before replacing) from one that is just older.
3
+ // Workspaces without a manifest (≤ 1.5.0): any file that differs from the new package is copied.
4
+ import { promises as fs } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { createHash } from 'node:crypto';
7
+ import { exists } from './fsx.js';
8
+
9
+ export const MANIFEST = path.join('_opencrew', 'manifest.json');
10
+
11
+ // Text files are hashed with LF line endings: an editor, git or a sync tool switching CRLF/LF
12
+ // must not make a file look "edited by the user". Binary files (with NUL bytes) are hashed as is.
13
+ const normalize = (buf) => {
14
+ const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
15
+ return b.includes(0) ? b : Buffer.from(b.toString('utf8').replace(/\r\n/g, '\n'));
16
+ };
17
+ const sha = (buf) => createHash('sha256').update(normalize(buf)).digest('hex');
18
+ const rel = (target, abs) => path.relative(target, abs).split(path.sep).join('/');
19
+
20
+ /** @returns {Promise<{files: Record<string,string>} | null>} null = no (or unreadable) manifest */
21
+ export async function readManifest(target) {
22
+ try {
23
+ const data = JSON.parse(await fs.readFile(path.join(target, MANIFEST), 'utf8'));
24
+ return data && typeof data.files === 'object' ? data : null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ export async function writeManifest(target, version, files) {
31
+ const sorted = Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b)));
32
+ await fs.writeFile(path.join(target, MANIFEST), JSON.stringify({ version, files: sorted }, null, 2) + '\n');
33
+ }
34
+
35
+ /**
36
+ * Delivery context shared by one init/update run.
37
+ * @param {string} target project root
38
+ * @param {{files:Record<string,string>}|null} manifest previous manifest (null = none)
39
+ */
40
+ export function newDelivery(target, manifest) {
41
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
42
+ return { target, manifest, backupDir: path.join(target, '.opencrew-backup', stamp), files: {}, copied: [], written: 0 };
43
+ }
44
+
45
+ /**
46
+ * Put `content` at `dest`. Missing → write. Equal → nothing. Different → only if `overwrite`,
47
+ * copying the current file to the backup dir first when the user edited it.
48
+ */
49
+ export async function deliverFile(ctx, dest, content, { overwrite }) {
50
+ const key = rel(ctx.target, dest);
51
+ const fresh = sha(content);
52
+ if (!(await exists(dest))) {
53
+ await fs.mkdir(path.dirname(dest), { recursive: true });
54
+ await fs.writeFile(dest, content);
55
+ ctx.files[key] = fresh;
56
+ ctx.written += 1;
57
+ return;
58
+ }
59
+ const current = sha(await fs.readFile(dest));
60
+ if (current === fresh) {
61
+ ctx.files[key] = fresh;
62
+ return;
63
+ }
64
+ if (!overwrite) return; // the user's version stays (and is not tracked)
65
+ const edited = ctx.manifest ? ctx.manifest.files[key] !== current : true;
66
+ if (edited) {
67
+ const copy = path.join(ctx.backupDir, key);
68
+ await fs.mkdir(path.dirname(copy), { recursive: true });
69
+ await fs.copyFile(dest, copy);
70
+ ctx.copied.push(key);
71
+ }
72
+ await fs.writeFile(dest, content);
73
+ ctx.files[key] = fresh;
74
+ ctx.written += 1;
75
+ }
76
+
77
+ /** deliverFile for every file under `src`, mirrored into `dest`. */
78
+ export async function deliverTree(ctx, src, dest, { overwrite, skip = () => false }) {
79
+ async function walk(dir) {
80
+ for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
81
+ const from = path.join(dir, entry.name);
82
+ const relPath = path.relative(src, from).split(path.sep).join('/');
83
+ if (skip(relPath)) continue;
84
+ if (entry.isDirectory()) await walk(from);
85
+ else await deliverFile(ctx, path.join(dest, relPath), await fs.readFile(from), { overwrite });
86
+ }
87
+ }
88
+ await walk(src);
89
+ }
@@ -0,0 +1,110 @@
1
+ // What `update` does beyond refreshing _opencrew/core and the catalog skills, so that every
2
+ // improvement reaches people who already use OpenCrew (AGENTS.md rule 14).
3
+ import { promises as fs } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { exists, writeBridgeFile } from './fsx.js';
6
+ import { IDES } from './ides.js';
7
+ import { deliverFile } from './manifest.js';
8
+
9
+ /** Semver compare (no pre-release tags): >0 if a > b, <0 if a < b, 0 if equal. */
10
+ export function compareVersions(a, b) {
11
+ const pa = String(a).split('.').map(Number);
12
+ const pb = String(b).split('.').map(Number);
13
+ for (let i = 0; i < 3; i++) {
14
+ const d = (pa[i] || 0) - (pb[i] || 0);
15
+ if (d) return d;
16
+ }
17
+ return 0;
18
+ }
19
+
20
+ const sharedPaths = (() => {
21
+ const count = new Map();
22
+ for (const ide of IDES) for (const f of ide.files) count.set(f.path, (count.get(f.path) ?? 0) + 1);
23
+ return new Set([...count].filter(([, n]) => n > 1).map(([p]) => p));
24
+ })();
25
+
26
+ async function hasOpencrew(file) {
27
+ return (await exists(file)) && /opencrew/i.test(await fs.readFile(file, 'utf8'));
28
+ }
29
+
30
+ /**
31
+ * IDEs installed in `target`, detected by their own bridge files (a path shared by several
32
+ * IDEs only counts for an IDE that has no file of its own — e.g. Codex).
33
+ */
34
+ export async function detectInstalledIdes(target) {
35
+ const found = [];
36
+ for (const ide of IDES) {
37
+ const own = ide.files.filter((f) => !sharedPaths.has(f.path));
38
+ const probes = own.length ? own : ide.files;
39
+ for (const f of probes) {
40
+ if (await hasOpencrew(path.join(target, f.path))) {
41
+ found.push(ide);
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ return found;
47
+ }
48
+
49
+ /** Rewrite the bridges of the installed IDEs only (frontmatter files whole, others by block). */
50
+ export async function refreshBridges(ctx, ides) {
51
+ const done = new Set();
52
+ for (const ide of ides) {
53
+ for (const f of ide.files) {
54
+ if (done.has(f.path)) continue;
55
+ done.add(f.path);
56
+ const file = path.join(ctx.target, f.path);
57
+ if (f.content.startsWith('---')) await deliverFile(ctx, file, f.content, { overwrite: true });
58
+ else await writeBridgeFile(file, f.content);
59
+ }
60
+ }
61
+ }
62
+
63
+ const OUTPUT_DIR = ['--output-dir', '_opencrew/logs/playwright'];
64
+
65
+ /**
66
+ * Merge the Playwright server of the template into the project's .mcp.json without touching
67
+ * other servers. @returns 'created' | 'updated' | 'unchanged' | 'invalid'
68
+ */
69
+ export async function mergeMcp(target, templateFile) {
70
+ const file = path.join(target, '.mcp.json');
71
+ const template = JSON.parse(await fs.readFile(templateFile, 'utf8'));
72
+ if (!(await exists(file))) {
73
+ await fs.writeFile(file, JSON.stringify(template, null, 2) + '\n');
74
+ return 'created';
75
+ }
76
+ let current;
77
+ try {
78
+ current = JSON.parse(await fs.readFile(file, 'utf8'));
79
+ } catch {
80
+ return 'invalid';
81
+ }
82
+ current.mcpServers ??= {};
83
+ const pw = current.mcpServers.playwright;
84
+ if (!pw) current.mcpServers.playwright = template.mcpServers.playwright;
85
+ else if (Array.isArray(pw.args) && !pw.args.includes('--output-dir')) pw.args.push(...OUTPUT_DIR);
86
+ else return 'unchanged';
87
+ await fs.writeFile(file, JSON.stringify(current, null, 2) + '\n');
88
+ return 'updated';
89
+ }
90
+
91
+ const LEGACY_ROOTS = ['.gemini/skills', '.claude/skills', '.agents/skills', '.agent/workflows', '.agent/rules'];
92
+
93
+ /** Old bridges that point to a system no longer installed (e.g. `_opensquad/`). Never deleted. */
94
+ export async function findLegacyBridges(target) {
95
+ if (await exists(path.join(target, '_opensquad'))) return [];
96
+ const found = [];
97
+ async function walk(dir, depth) {
98
+ let entries;
99
+ try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return; }
100
+ for (const e of entries) {
101
+ const p = path.join(dir, e.name);
102
+ if (e.isDirectory() && depth < 2) await walk(p, depth + 1);
103
+ else if (e.isFile() && e.name.endsWith('.md') && /_opensquad\//.test(await fs.readFile(p, 'utf8'))) {
104
+ found.push(path.relative(target, p).split(path.sep).join('/'));
105
+ }
106
+ }
107
+ }
108
+ for (const root of LEGACY_ROOTS) await walk(path.join(target, root), 0);
109
+ return found;
110
+ }
@@ -3,7 +3,7 @@
3
3
  "mcpServers": {
4
4
  "playwright": {
5
5
  "command": "npx",
6
- "args": ["@playwright/mcp@0.0.78", "--config", "_opencrew/config/playwright.config.json"]
6
+ "args": ["@playwright/mcp@0.0.78", "--config", "_opencrew/config/playwright.config.json", "--output-dir", "_opencrew/logs/playwright"]
7
7
  }
8
8
  }
9
9
  }