@aksp/opencrew 1.6.0 → 1.6.2

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 (25) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +19 -5
  3. package/package.json +1 -1
  4. package/src/cli.js +2 -1
  5. package/src/commands/init.js +26 -18
  6. package/src/lib/migrations.js +40 -3
  7. package/templates/_opencrew/.opencrew-version +1 -1
  8. package/templates/_opencrew/core/prompts/build.prompt.md +2 -0
  9. package/templates/_opencrew/core/runner.pipeline.md +54 -26
  10. package/templates/_opencrew/core/scripts/comum.mjs +48 -0
  11. package/templates/_opencrew/core/scripts/conferir-fontes/busca.mjs +82 -0
  12. package/templates/_opencrew/core/scripts/conferir-fontes/coleta.mjs +104 -0
  13. package/templates/_opencrew/core/scripts/conferir-fontes/relatorio.mjs +52 -0
  14. package/templates/_opencrew/core/scripts/conferir-fontes.mjs +82 -136
  15. package/templates/_opencrew/core/scripts/verificar/argumentos.mjs +33 -0
  16. package/templates/_opencrew/core/scripts/verificar/arquivos.mjs +50 -0
  17. package/templates/_opencrew/core/scripts/verificar/html.mjs +52 -0
  18. package/templates/_opencrew/core/scripts/verificar/leitura.mjs +138 -71
  19. package/templates/_opencrew/core/scripts/verificar/medicao.mjs +142 -0
  20. package/templates/_opencrew/core/scripts/verificar/pecas.mjs +190 -0
  21. package/templates/_opencrew/core/scripts/verificar/proibicoes.mjs +100 -0
  22. package/templates/_opencrew/core/scripts/verificar/regras.mjs +142 -92
  23. package/templates/_opencrew/core/scripts/verificar/relatorio.mjs +43 -0
  24. package/templates/_opencrew/core/scripts/verificar/secoes.mjs +145 -0
  25. package/templates/_opencrew/core/scripts/verificar.mjs +162 -88
package/CHANGELOG.md CHANGED
@@ -3,6 +3,81 @@
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.2] — 2026-10-05
7
+
8
+ Correção da 1.6.1. Chega a quem já usa com um `npx @aksp/opencrew@latest update`.
9
+
10
+ ### Fixed
11
+ - **Node 20: o verificador não esgota mais a memória com texto grande.** Um post, tweet ou
12
+ legenda de dezenas de milhares de caracteres numa peça só derrubava o verificador no Node 20,
13
+ sem relatório (o runner avisava que a verificação não rodou). A contagem de caracteres passou a
14
+ ser feita em janelas, com o mesmo resultado. O defeito vinha da 1.5.0; Node 22 e 24 não o
15
+ tinham.
16
+
17
+ ### Internal
18
+ - Release: a tag só sai depois do CI verde nas quatro células (Ubuntu e Windows, Node 20 e 22).
19
+ A 1.6.1 foi publicada com o CI do Node 20 vermelho, porque a publicação roda só no Node 22.
20
+
21
+ ## [1.6.1] — 2026-10-05
22
+
23
+ Fase R1 "Reparos da 1.6.0: o verificador mede de verdade" (`specs/fase-r1-reparos-1-6-1.md`),
24
+ vinda da revisão das specs (`docs/auditoria/2026-10-04-revisao-specs.md`). Chega a quem já usa com
25
+ um `npx @aksp/opencrew@latest update`.
26
+
27
+ ### Fixed
28
+ - **O verificador mede o texto escrito com rótulos**, do jeito que os próprios best-practices
29
+ ensinam (`=== CAPTION ===`, `=== HASHTAGS ===`, `=== SLIDES ===`, `=== HOOK ===`, `=== TWEET ===`,
30
+ `=== TITLE ===`). Antes respondia "Nada a apontar" sem medir.
31
+ - **"Não medido" é dito**: com o formato informado e a peça principal não achada, o relatório
32
+ alerta em vez de aprovar em silêncio. O resumo passa a ser
33
+ `X bloqueios, Y alertas, Z não medidos`.
34
+ - **Bloqueios falsos**: cor hexadecimal (`#666666`), número comum, CEP e "XXX Congresso" não são
35
+ mais "Placeholder"; `{{name}}` em e-mail e WhatsApp vira nota; termo proibido vale como palavra
36
+ inteira ("IA" não bloqueia "dia a dia"); o termo que o usuário mandou preferir não é proibido.
37
+ - **Arquivo local sem limites não desliga o verificador**: os limites de
38
+ `_opencrew/best-practices.local/` somam aos do core, chave a chave.
39
+ - **Erros que passavam por OK**: rodar fora da pasta do projeto ou com crew inexistente agora dá
40
+ erro (código 1), sem linha de status; um arquivo ausente na lista não derruba a verificação dos
41
+ outros; imagem e `.docx` não são lidos como texto; no HTML, só o texto visível e os links;
42
+ arquivo de texto fora do UTF-8 vira alerta "Não verificado" (UTF-16 com marca é lido).
43
+ - **Relatório que não saía**: com o projeto aberto por junção ou link de pasta, os dois scripts
44
+ terminavam sem imprimir nada.
45
+ - **Várias peças no mesmo arquivo** são medidas uma a uma (três posts não viram uma soma); a linha
46
+ `---` não encerra mais a seção; slides contados nas escritas comuns ("📌 Slide 2", "Slide #3").
47
+ Título e meta description em bloco YAML (`>-`, `|`) são medidos inteiros; `[PREENCHER: …]`
48
+ longo não escapa; imagem e link de âncora não contam como link.
49
+ - **Conferência de fontes**: enxerga `caminho:` com comentário ou aspas e os arquivos de `agents/`
50
+ (agentes e tasks); recusa crew de fora do projeto; mensagens corrigidas ("Não há correção
51
+ automática…", aviso de busca parcial); caminho com marcador de modelo (`AAAA-MM-DD`) e comando
52
+ entre crases não são conferidos; o `--corrigir` troca só o caminho citado (antes trocava
53
+ qualquer trecho igual) e nunca aponta o destino de gravação de um agente para um arquivo que
54
+ já existe.
55
+ - **`init --repair-bridges`** sem `--ide` regrava só as IDEs instaladas (antes criava as pontes
56
+ das 9); `--all` regrava todas; o resumo lista as cópias de segurança. Numa pasta sem workspace,
57
+ para com erro em vez de instalar; num workspace sem manifesto, não cria um.
58
+
59
+ ### Changed
60
+ - **Runner**: passa o formato de cada arquivo ao verificador (`caminho=formato`); laço de revisão
61
+ com 3 ciclos por padrão (`max_review_cycles`) e saída também quando não há bloqueio; regras do
62
+ revisor injetadas em toda execução (valem para crews já criadas); avisa quando um script não
63
+ rodou; a conferência de fontes roda antes de carregar as fontes; a aprovação final mostra o que
64
+ ficou sem medir e as notas do relatório.
65
+ - Crew nova recebe o limite de ciclos de revisão pelo tier: Express 1, Standard 2, Full 3.
66
+ - Em blog, a seção com cabeçalho de outro canal ("Como postar no LinkedIn") não é medida como
67
+ post, e o relatório diz isso ("Não medido").
68
+ - Hashtags sob um cabeçalho que cita o canal ("Hashtags LinkedIn") somam à peça desse canal.
69
+ - "Aceitar assim mesmo" não promete mais registro: o registro chega com a entrega por canal.
70
+ - Texto solto depois de uma linha `---` passa a contar na peça de cima. Telefone falso de 8 ou 9
71
+ dígitos, fora de link, deixa de ser pego.
72
+
73
+ ### Internal
74
+ - Scripts do runtime em módulos (`verificar/`, `conferir-fontes/`, `comum.mjs`); leitor de peças
75
+ exportado para a próxima fase. Todos os cenários R1 com teste de mesmo ID; teste de upgrade
76
+ 1.6.0 → 1.6.1.
77
+ - Revisão do código antes da tag: sete leituras independentes e duas rodadas de conserto com
78
+ teste; o que ficou adiado tem destino na spec (§11 e §12).
79
+ - Revisão das specs (265 achados) e faxina de documentos; specs R1, U3a e U3b.
80
+
6
81
  ## [1.6.0] — 2026-10-02
7
82
 
8
83
  Trilha U2 "Crew que conhece o projeto" + U6 "Convivência" (`specs/fase-u2-crew-que-conhece-o-projeto.md`).
@@ -32,6 +107,7 @@ Trilha U2 "Crew que conhece o projeto" + U6 "Convivência" (`specs/fase-u2-crew-
32
107
  - Migração do formato de memória faz `memories.md.bak` e avisa (fim do reset silencioso); regra única
33
108
  sobre o que vai para a memória (só feedback explícito).
34
109
  - `_build/discovery.yaml` agora em `crews/{code}/_build/`; build grava caminhos relativos à raiz.
110
+
35
111
  ## [1.5.0] — 2026-10-02
36
112
 
37
113
  Trilha U1 "Revisor com dentes" — primeira melhoria vinda do uso real
package/README.md CHANGED
@@ -143,6 +143,12 @@ enxuto — todos apontam para a mesma fonte.
143
143
  | `QWEN.md` (ponte) + `.agents/skills/opencrew/SKILL.md` | Qwen Code |
144
144
  | `AGENTS.md` (ponte) + `.trae/rules/opencrew.md` | Trae |
145
145
 
146
+ > **Claude Cowork (modo alternativo):** o Cowork não reconhece o comando `/opencrew` (ele não lê
147
+ > skills de dentro da pasta do projeto). Funciona assim: abra a pasta do projeto e peça, em texto:
148
+ > *"Leia o arquivo `_opencrew/core/system.md` deste projeto e siga as instruções dele. Mostre o
149
+ > menu principal."* Depois use frases como "rodar a crew blog-semanal" no lugar dos comandos com `/`.
150
+
151
+
146
152
  > ⚠️ **Importante:** `CLAUDE.md`, `GEMINI.md` e os demais arquivos de IDE são
147
153
  > pontes geradas automaticamente. Eles são finos (5-10 linhas) e usam blocos
148
154
  > marcados (`<!-- opencrew:start/end -->`) que permitem **merge não-destrutivo**
@@ -198,7 +204,7 @@ meu-projeto/
198
204
 
199
205
  > O dashboard visual (`dashboard/index.html`) **não é instalado** pelo `init` — ele vive
200
206
  > só no repositório do OpenCrew e ainda é experimental (decisão de publicar ou remover:
201
- > Fase 4 da auditoria em `docs/auditoria/`).
207
+ > fase U3a — ver `IDEIAS.md` no repositório).
202
208
 
203
209
  ---
204
210
 
@@ -224,12 +230,20 @@ o que você fez:
224
230
  - **Versão mais nova instalada?** O `update` não volta para uma versão mais antiga (cache do
225
231
  `npx`): ele para e pede `npx @aksp/opencrew@latest update`.
226
232
 
227
- Para regravar as pontes de IDEs específicas (ou adicionar uma IDE nova):
233
+ Para regravar as pontes de IDE num workspace que já existe:
228
234
 
229
235
  ```bash
230
- npx @aksp/opencrew init --repair-bridges --ide=claude-code
236
+ npx @aksp/opencrew@latest init --repair-bridges # só as IDEs que você já tem instaladas
237
+ npx @aksp/opencrew@latest init --repair-bridges --ide=claude-code # só as indicadas (ou uma IDE nova)
238
+ npx @aksp/opencrew@latest init --repair-bridges --all # as 9 IDEs
231
239
  ```
232
240
 
241
+ Sem `--ide` e sem `--all`, o `init --repair-bridges` usa a mesma detecção do `update` (aqui o
242
+ `--yes` não escolhe IDE); se não encontra nenhuma ponte, para com erro e pede `--ide=<id>`.
243
+ O reparo não instala: numa pasta sem workspace do OpenCrew ele para com erro e pede o `init`.
244
+ Ponte de arquivo inteiro que você editou (ex.: `.claude/skills/opencrew/SKILL.md`) é copiada
245
+ antes para `.opencrew-backup/<data>/`, e o resumo do `init --repair-bridges` lista cada cópia.
246
+
233
247
  Se você está migrando de uma versão anterior a v1.3, o `update` detecta
234
248
  AGENTS.md legados (sistema completo de 150 linhas) e os substitui pela ponte
235
249
  fina. Desde a v1.4.2 o arquivo original é copiado antes para `AGENTS.md.bak` (até a v1.4.1,
@@ -268,12 +282,12 @@ npx @aksp/opencrew update --check
268
282
  | Comando | O que faz |
269
283
  |---|---|
270
284
  | `npx @aksp/opencrew init` | Instala o OpenCrew na pasta atual |
271
- | `npx @aksp/opencrew update` | Atualiza o framework |
285
+ | `npx @aksp/opencrew@latest update` | Atualiza o framework |
272
286
  | `npx @aksp/opencrew update --check` (ou `--dry-run`) | Verifica se há update disponível, sem alterar nada |
273
287
  | `npx @aksp/opencrew upgrade` | Atalho para `update` |
274
288
  | `npx @aksp/opencrew init --ide=claude-code,cursor` | Instala só as pontes das IDEs indicadas |
275
289
  | `npx @aksp/opencrew init --all` (ou `-y`) | Instala as pontes de todas as IDEs |
276
- | `npx @aksp/opencrew init --repair-bridges` | Regrava as pontes de IDE num workspace existente |
290
+ | `npx @aksp/opencrew@latest init --repair-bridges` | Regrava as pontes das IDEs já instaladas num workspace existente (`--ide=a,b`: só as indicadas; `--all`: as 9) |
277
291
  | `npx @aksp/opencrew version` | Mostra a versão instalada |
278
292
  | `npx @aksp/opencrew help` | Mostra ajuda dos comandos CLI |
279
293
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aksp/opencrew",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/cli.js CHANGED
@@ -103,7 +103,8 @@ ${c.bold('Options for init')}
103
103
  --ide=a,b Preselect IDEs (skip the prompt). Valid: ${allIdeIds().join(', ')}
104
104
  --all Configure every supported IDE
105
105
  --yes, -y Non-interactive; accept defaults
106
- --repair-bridges Regenerate IDE bridge files in an existing workspace
106
+ --repair-bridges Rewrite the bridges of the IDEs already installed in an existing
107
+ workspace (with --ide: only those; with --all: every IDE)
107
108
 
108
109
  ${c.bold('Options for update')}
109
110
  --check Report whether an update is available without making changes
@@ -6,6 +6,7 @@ import { newDelivery, deliverTree, deliverFile, writeManifest, readManifest } fr
6
6
  import { ideById, allIdeIds, AGENTS_BRIDGE } from '../lib/ides.js';
7
7
  import { pickIdes as promptIdes } from '../lib/prompts.js';
8
8
  import { UsageError } from '../lib/errors.js';
9
+ import { repairIdeIds, backupSummary, recordRepair, NO_BRIDGES_FOUND, NO_WORKSPACE } from '../lib/migrations.js';
9
10
  import { c, log, info, ok, warn, step } from '../lib/ui.js';
10
11
 
11
12
  const STAMP = path.join('_opencrew', '.opencrew-version');
@@ -22,25 +23,13 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
22
23
  const version = pkg.version;
23
24
  const state = await workspaceState(target);
24
25
 
25
- // --repair-bridges mode: regenerate IDE bridge files in an existing workspace.
26
- if (opts['repair-bridges'] && state !== 'none') {
27
- const ids = await resolveIdes(opts, async () => allIdeIds());
28
- log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — repairing IDE bridges`);
29
- log(c.dim(`Target: ${target}\n`));
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 });
34
-
35
- log(`\n${c.green(c.bold('Done!'))} IDE bridges regenerated.\n`);
36
- log(`${c.bold('Next step:')} Restart your IDE, then type ${c.cyan('/opencrew')} to verify.\n`);
37
- return;
38
- }
26
+ if (opts['repair-bridges'] && state === 'none') throw new UsageError(NO_WORKSPACE); // before any write
27
+ if (opts['repair-bridges']) return repairBridges(target, version, opts);
39
28
 
40
29
  if (state === 'complete') {
41
30
  warn('An opencrew workspace already exists here.');
42
- info(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew update')}`);
43
- info(`To repair IDE bridges, use: ${c.cyan('npx @aksp/opencrew init --repair-bridges')}`);
31
+ info(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew@latest update')}`);
32
+ info(`To repair IDE bridges, use: ${c.cyan('npx @aksp/opencrew@latest init --repair-bridges')}`);
44
33
  info(`To reinstall from scratch, delete _opencrew/ first, then run init again.`);
45
34
  return;
46
35
  }
@@ -103,6 +92,25 @@ export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
103
92
  log(` No API keys needed up front — opencrew asks for them in chat only if a skill you use requires one.\n`);
104
93
  }
105
94
 
95
+ /**
96
+ * --repair-bridges: rewrite IDE bridge files in an existing workspace. --ide wins (even next
97
+ * to --all); --all alone means every IDE; otherwise only the IDEs `update` would detect.
98
+ */
99
+ async function repairBridges(target, version, opts) {
100
+ const ids = await resolveIdes({ ide: opts.ide }, () => repairIdeIds(target, opts));
101
+ if (!ids.length) throw new UsageError(NO_BRIDGES_FOUND); // before the first write
102
+ log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — repairing IDE bridges`);
103
+ log(c.dim(`Target: ${target}\n`));
104
+ const ctx = newDelivery(target, await readManifest(target));
105
+ await writeBridges(target, ids, { overwrite: true, ctx });
106
+ await recordRepair(ctx, version); // only where a manifest already exists
107
+ const [copied, ...copies] = backupSummary(ctx);
108
+ if (copied) warn(copied);
109
+ for (const copy of copies) log(copy);
110
+ log(`\n${c.green(c.bold('Done!'))} IDE bridges regenerated.\n`);
111
+ log(`${c.bold('Next step:')} Restart your IDE, then type ${c.cyan('/opencrew')} to verify.\n`);
112
+ }
113
+
106
114
  /**
107
115
  * Copy the framework payload into `target` without overwriting anything.
108
116
  * Never copies the version stamp: only a finished init writes it.
@@ -130,8 +138,8 @@ async function workspaceState(target) {
130
138
 
131
139
  /**
132
140
  * Decide which IDEs to configure. --all / --yes → every IDE; --ide → validated list;
133
- * nothing → `fallback()` (the interactive prompt). Throws UsageError if --ide names no
134
- * valid IDE.
141
+ * nothing → `fallback()` (the interactive prompt; in repair mode, the detection). Throws
142
+ * UsageError if --ide names no valid IDE.
135
143
  */
136
144
  async function resolveIdes(opts, fallback) {
137
145
  if (opts.all || opts.yes) return allIdeIds();
@@ -1,10 +1,11 @@
1
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).
2
+ // improvement reaches people who already use OpenCrew (AGENTS.md rule 14). The IDE detection
3
+ // is shared with `init --repair-bridges`, whose helpers live here too.
3
4
  import { promises as fs } from 'node:fs';
4
5
  import path from 'node:path';
5
6
  import { exists, writeBridgeFile } from './fsx.js';
6
- import { IDES } from './ides.js';
7
- import { deliverFile } from './manifest.js';
7
+ import { IDES, allIdeIds } from './ides.js';
8
+ import { deliverFile, writeManifest } from './manifest.js';
8
9
 
9
10
  /** Semver compare (no pre-release tags): >0 if a > b, <0 if a < b, 0 if equal. */
10
11
  export function compareVersions(a, b) {
@@ -46,6 +47,42 @@ export async function detectInstalledIdes(target) {
46
47
  return found;
47
48
  }
48
49
 
50
+ /**
51
+ * IDE ids `init --repair-bridges` rewrites when --ide is not given: every IDE with --all,
52
+ * otherwise the ones `update` would detect. --yes chooses nothing here.
53
+ */
54
+ export async function repairIdeIds(target, { all } = {}) {
55
+ if (all) return allIdeIds();
56
+ return (await detectInstalledIdes(target)).map((ide) => ide.id);
57
+ }
58
+
59
+ /** `init --repair-bridges` found no bridge and got neither --ide nor --all. */
60
+ export const NO_BRIDGES_FOUND =
61
+ `Não encontrei pontes de IDE aqui. Use \`--ide=<id>\` para escolher. Ids válidos: ${allIdeIds().join(', ')}.`;
62
+
63
+ /** `init --repair-bridges` in a folder that is not a workspace: the repair never installs. */
64
+ export const NO_WORKSPACE =
65
+ 'Não encontrei um workspace do OpenCrew nesta pasta. O reparo não instala: para instalar, rode `npx @aksp/opencrew@latest init`.';
66
+
67
+ /**
68
+ * Add the bridges a repair rewrote to the manifest — only when the workspace has one. Without
69
+ * it (installed up to 1.5.0) none is created: a bridges-only manifest would make the next
70
+ * `update` call every older file "edited by you" and hide its first-protected-update notice.
71
+ */
72
+ export async function recordRepair(ctx, version) {
73
+ if (ctx.manifest) await writeManifest(ctx.target, version, { ...ctx.manifest.files, ...ctx.files });
74
+ }
75
+
76
+ /** Summary lines of a delivery's backup copies, each with its path in .opencrew-backup/<date>/. */
77
+ export function backupSummary(ctx) {
78
+ if (!ctx.copied.length) return [];
79
+ const dir = path.relative(ctx.target, ctx.backupDir).split(path.sep).join('/');
80
+ return [
81
+ `${ctx.copied.length} cópia(s) de segurança feita(s) antes de regravar:`,
82
+ ...ctx.copied.map((file) => ` ${dir}/${file}`),
83
+ ];
84
+ }
85
+
49
86
  /** Rewrite the bridges of the installed IDEs only (frontmatter files whole, others by block). */
50
87
  export async function refreshBridges(ctx, ides) {
51
88
  const done = new Set();
@@ -1 +1 @@
1
- 1.6.0
1
+ 1.6.2
@@ -410,6 +410,8 @@ side_effects: irreversible # REQUIRED for any step that publishes, posts, sends
410
410
  # distributes outside the project (it cannot be undone). The Pipeline
411
411
  # Runner never retries these automatically, and Gate 2c places them last.
412
412
  # Omit for every other step.
413
+ max_review_cycles: {N} # ONLY for the review step: write it next to its `on_reject`.
414
+ # By crew tier (`crew.tier` in design.yaml): Express 1, Standard 2, Full 3.
413
415
  ---
414
416
  ```
415
417
 
@@ -85,16 +85,9 @@ Before starting execution:
85
85
  ```
86
86
  - Do not pause execution for this migration (the one-line notice above is enough).
87
87
 
88
- 1c. **Project sources (`fontes:`)** — if `crew.yaml` has a `fontes:` list (files or folders of
89
- the user's project, paths relative to the project root), read them now: a file in full up to
90
- ~300 lines, otherwise its headings plus the passages relevant to this run's task; a folder as
91
- its file list. Treat them as the **truth of the project**: when they disagree with the
92
- briefing, the research or your own assumptions, the sources take precedence over them
93
- (as fontes valem sobre o briefing e a pesquisa) — and say so when it matters.
94
-
95
- 1d. **Source check** — before the first step, run:
88
+ 1c. **Source check** — before loading the project sources (1d), run:
96
89
  ```bash
97
- node _opencrew/core/scripts/conferir-fontes.mjs --crew crews/{name}
90
+ node _opencrew/core/scripts/conferir-fontes.mjs --crew "crews/{name}"
98
91
  ```
99
92
  If the last line is `FONTES:PENDENTE` (a cited file was moved, renamed or deleted), show the
100
93
  report and ask — never continue silently with a missing source:
@@ -106,8 +99,19 @@ Before starting execution:
106
99
  2. Seguir assim mesmo
107
100
  3. Parar
108
101
  ```
109
- On 1, run the same command with `--corrigir` and show the new result. Not-portable alerts
110
- (absolute paths) are mentioned once, without stopping.
102
+ On 1, run the same command with `--corrigir`, show the new result and re-read `crew.yaml` and
103
+ any agent file already loaded (it may have changed them); 1d then loads the sources from the
104
+ corrected paths. If the new result still ends in `FONTES:PENDENTE`, ask again with options 2 and
105
+ 3 only. Not-portable alerts (absolute paths) are mentioned once, without stopping. If the script
106
+ did not run (no Node, an error, or no `FONTES:` status line), tell the user "⚠️ A conferência de
107
+ fontes não rodou: {motivo}" and continue; the final approval repeats the warning.
108
+
109
+ 1d. **Project sources (`fontes:`)** — if `crew.yaml` has a `fontes:` list (files or folders of
110
+ the user's project, paths relative to the project root), read them now: a file in full up to
111
+ ~300 lines, otherwise its headings plus the passages relevant to this run's task; a folder as
112
+ its file list. Treat them as the **truth of the project**: when they disagree with the
113
+ briefing, the research or your own assumptions, the sources take precedence over them
114
+ (as fontes valem sobre o briefing e a pesquisa) — and say so when it matters.
111
115
 
112
116
  2. Read `crews/{name}/pipeline/pipeline.yaml` for the pipeline definition
113
117
  3. **Resolve skills**: Read `crew.yaml` → `skills` section. For each non-native skill (anything other than web_search, web_fetch):
@@ -349,6 +353,16 @@ Before executing any step that references an agent:
349
353
  - Faltou um dado real? Escreva [PREENCHER: o que falta] no lugar — o usuário completa
350
354
  na aprovação final. Um [PREENCHER] honesto vale mais que um exemplo inventado.
351
355
  ```
356
+ g. **Reviewer rules (always)** — for every step with `on_reject:`, inject at the same point:
357
+ ```
358
+ --- REGRAS DO REVISOR ---
359
+ - Copie os valores medidos do relatório; nunca estime contagens.
360
+ - Bloqueio no relatório é REJECT, seja qual for a nota — menos [PREENCHER], que o usuário
361
+ resolve na aprovação final.
362
+ - Alerta não resolvido nem justificado limita a nota a 7/10.
363
+ - O checklist só marca o que o relatório confirma; item "não medido" ou "não verificado" é
364
+ dito assim, nunca como aprovado.
365
+ ```
352
366
 
353
367
  ### Context Compression (Summary-Based Handoff)
354
368
 
@@ -564,7 +578,8 @@ Apply this transformation consistently for every write in this step.
564
578
  a fact, a format), write it to `crews/{name}/_memory/memories.md` in the matching section
565
579
  **before the next step** (antes do próximo passo) — not only at the end of the run, which may
566
580
  never come. A term the user asked to remove goes to `## Proibições Explícitas` **between
567
- quotes** (entre aspas: `- Nunca usar "termo"`), so the automatic checker blocks it next time.
581
+ quotes** (entre aspas), in the canonical form — `- Nunca usar "termo"` or, with a replacement,
582
+ `- Nunca usar "termo" → usar "outro"` — so the automatic checker blocks it next time.
568
583
  - **Correction vs. company profile**: if the correction contradicts `_opencrew/_memory/company.md`
569
584
  (e.g. the organization's name, the main audience), ask: "Isso vale para todas as crews?
570
585
  Atualizo o perfil da empresa?" — change `company.md` only after a yes.
@@ -678,36 +693,48 @@ catching obvious issues early and reducing review cycle waste.
678
693
  When a step has `on_reject: {step-id}` (a review step):
679
694
 
680
695
  1. **Automatic check BEFORE the reviewer runs** — run the checker on **all outputs** (todas as
681
- saídas) of every non-checkpoint step from the `on_reject` step up to the step right before
682
- the review, using the transformed paths of this run (run_id/vN):
696
+ saídas) of every non-checkpoint step from the `on_reject` step up to the step right before the
697
+ review, using the transformed paths of this run (run_id/vN). Each item is `caminho=formato`, with
698
+ the `format:` of the step that generated that file; a step with no `format:`, with an export
699
+ format (`pdf`, `csv`, `formatted-post`) or with one outside `[a-z0-9-]+` goes without `=formato`:
683
700
  ```bash
684
- node _opencrew/core/scripts/verificar.mjs --crew crews/{name} --arquivo "{path1},{path2},…" --formato {blog format id of those steps, if any}
701
+ node _opencrew/core/scripts/verificar.mjs --crew "crews/{name}" --arquivo "{path1}={format1},{path2},…"
685
702
  ```
686
703
  Save the full output to `crews/{name}/output/{run_id}/verificacao-ciclo-{N}.md` and inject it
687
704
  into the reviewer's context as `--- VERIFICAÇÃO AUTOMÁTICA ---`. The reviewer must copy the
688
- measured values from it (see best-practices `review.md`). If the command itself fails (no
689
- Node, unexpected error), tell the user "⚠️ A verificação automática não rodou: {motivo}" and
690
- continue with the normal review.
705
+ measured values from it (see best-practices `review.md`). If the checker did not run (no Node,
706
+ an error, or no `VERIFICACAO:` status line), tell the user, continue with the normal review and
707
+ repeat it at the final approval: "⚠️ A verificação automática não rodou: {motivo}".
691
708
  2. **A block cannot be approved** — if the last line of the checker output is
692
709
  `VERIFICACAO:BLOQUEADA`, the verdict is **REJECT** regardless of the score (qualquer que seja a
693
710
  nota). Send the report (blocks first) to the writer together with the reviewer's feedback.
694
711
  If the last line is `VERIFICACAO:AGUARDANDO_USUARIO`, the only blocks are `[PREENCHER: …]`
695
712
  (real data only the user has): do NOT reject for them — the reviewer judges the rest, and the
696
713
  final approval below collects the missing data from the user.
697
- 3. Track the review cycle count. If the reviewer rejects, go back to the referenced step.
698
- 4. If max_review_cycles is reached with blocks remaining, present the report to the user:
714
+ 3. Track the review cycle count: a **cycle** is one pass of the reviewer. The maximum is
715
+ `max_review_cycles`, an integer from 1 declared where the step declares `on_reject` (the step
716
+ frontmatter or its `pipeline.yaml` entry); absent or invalid: 3. On every rejection, with or
717
+ without a block, send the reviewer's feedback to the writer and go back to the referenced step.
718
+ 4. If the last allowed pass also rejects, stop; the status of the last report picks the message, as
719
+ in item 2 — `VERIFICACAO:BLOQUEADA`: the blocks; any other status: the reviewer's feedback, also
720
+ with `VERIFICACAO:AGUARDANDO_USUARIO` (its only blocks are `[PREENCHER: …]`). Same three options:
699
721
  ```
700
- ⚠️ A revisão ainda encontra bloqueios depois de {N} ciclos:
722
+ {if VERIFICACAO:BLOQUEADA} ⚠️ A revisão ainda encontra bloqueios depois de {N} ciclos:
701
723
  {lista de bloqueios do relatório}
724
+ {any other status} A revisão não aprovou o texto depois de {N} ciclos. Motivo: {parecer resumido}
702
725
 
703
726
  1. Corrigir eu mesmo (eu edito o texto e você verifica de novo)
704
- 2. Aceitar assim mesmo (fica registrado no histórico da execução)
727
+ 2. Aceitar assim mesmo
705
728
  3. Abortar
706
729
  ```
707
730
  5. **Final approval checkpoint** (the checkpoint after the review): show the summary of the last
708
- report — `Verificação automática: {N} bloqueios, {M} alertas` — plus the list of alerts. If the
709
- approved text still contains `[PREENCHER: …]`, ask the user for each missing piece of real
710
- information and write it into the text before approving.
731
+ report — `Verificação automática: {N} bloqueios, {M} alertas, {Z} não medidos` — plus the list
732
+ of alerts and the {Z} items not measured or not verified (the `Não medido` and `Não verificado`
733
+ lines under each file, not the "não é texto" line of **Notas**), one per line as
734
+ `{arquivo} — {motivo}`, then the lines under `**Notas:**` in that report, as they are written,
735
+ and repeat every "não rodou" warning of this run (checker and source check). If the approved
736
+ text still contains `[PREENCHER: …]`, ask the user for each missing piece of real information
737
+ and write it into the text before approving.
711
738
 
712
739
  ### Dashboard Handoff (between steps)
713
740
 
@@ -796,7 +823,8 @@ This archives the run state for the `runs` command while keeping crew history av
796
823
  - Writing style choices → `## Estilo de Escrita`
797
824
  - Visual/design preferences → `## Design Visual`
798
825
  - Content structure choices → `## Estrutura de Conteúdo`
799
- - Explicit rejections or prohibitions → `## Proibições Explícitas`
826
+ - Explicit rejections or prohibitions → `## Proibições Explícitas`, in the canonical form
827
+ (`- Nunca usar "termo"` or `- Nunca usar "termo" → usar "outro"`)
800
828
  - Crew-specific technical patterns → `## Técnico (específico do crew)`
801
829
 
802
830
  **Never write to `memories.md`:**
@@ -0,0 +1,48 @@
1
+ // Validações e mensagens de erro de uso comuns aos scripts do runtime (verificar,
2
+ // conferir-fontes…). Node puro, sem dependências.
3
+ // Spec: specs/fase-r1-reparos-1-6-1.md, regra 13 (repositório do OpenCrew).
4
+ import { existsSync, realpathSync, statSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ export const MSG = {
9
+ faltaOpcao: (opcao) => `Falta a opção obrigatória ${opcao}.`,
10
+ semRaiz: 'Não encontrei `_opencrew/` nesta pasta. Rode o comando a partir da pasta do projeto.',
11
+ foraDoProjeto: (caminho) => `Caminho fora do projeto: ${caminho}`,
12
+ crewNaoEncontrada: (crew) => `Crew não encontrada: ${crew}`,
13
+ };
14
+
15
+ /** O caminho (relativo à raiz ou absoluto) fica dentro da raiz do projeto? */
16
+ export function dentroDoProjeto(raiz, caminho) {
17
+ const rel = path.relative(raiz, path.resolve(raiz, caminho));
18
+ return !(rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel));
19
+ }
20
+
21
+ /**
22
+ * O script foi chamado direto (`node …/script.mjs`)? Compara os caminhos reais: com o projeto
23
+ * aberto por uma junção ou um link de pasta, o Node resolve o link em `import.meta.url` e não em
24
+ * `process.argv[1]`, e a comparação dos textos daria falso — o script sairia sem imprimir nada.
25
+ */
26
+ export function ehPrincipal(metaUrl) {
27
+ try {
28
+ return Boolean(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(metaUrl));
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ const ehPasta = (p) => existsSync(p) && statSync(p).isDirectory();
35
+
36
+ /**
37
+ * Erro de uso, na ordem da regra 13: opção obrigatória faltando → pasta atual sem `_opencrew/`
38
+ * → crew ou caminho fora do projeto (antes de testar se existe) → crew inexistente.
39
+ * @returns {string|null} a mensagem em PT-BR, ou null quando está tudo certo
40
+ */
41
+ export function erroDeUso({ raiz, faltando = [], crew, caminhos = [] }) {
42
+ if (faltando.length) return MSG.faltaOpcao(faltando[0]);
43
+ if (!ehPasta(path.join(raiz, '_opencrew'))) return MSG.semRaiz;
44
+ const fora = [crew, ...caminhos].find((c) => !dentroDoProjeto(raiz, c));
45
+ if (fora) return MSG.foraDoProjeto(fora);
46
+ if (!ehPasta(path.resolve(raiz, crew))) return MSG.crewNaoEncontrada(crew);
47
+ return null;
48
+ }
@@ -0,0 +1,82 @@
1
+ // Busca da conferência de fontes: onde está cada caminho citado (crew → raiz do projeto →
2
+ // absoluto → ao lado do agente ou da task que cita) e, quando ele sumiu, o que existe no projeto
3
+ // com o mesmo nome. Só testa existência e lista nomes; nunca lê conteúdo.
4
+ // Spec: specs/fase-r1-reparos-1-6-1.md, regra 17 (repositório do OpenCrew).
5
+ import { readdir } from 'node:fs/promises';
6
+ import { existsSync } from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ export const LIMITE_DA_BUSCA = 20000;
10
+ const IGNORAR = new Set(['node_modules', 'output', '_opencrew', '_build']);
11
+
12
+ export const barra = (p) => p.split(path.sep).join('/');
13
+ export const ehAbsoluto = (p) => /^[A-Za-z]:[\\/]/.test(p) || p.startsWith('/');
14
+ export const temBarraFinal = (ref) => /[\\/]$/.test(ref);
15
+ const semBarraFinal = (ref) => ref.replace(/[\\/]+$/, '');
16
+
17
+ const SUFIXO_DE_AGENTE = '.agent.md';
18
+
19
+ /**
20
+ * Pastas ao lado de quem cita, só para arquivo de agente ou de task (dentro de `agents/`): a do
21
+ * próprio arquivo e, para `agents/X.agent.md`, também `agents/X/` — é lá que fica a task que o
22
+ * frontmatter do agente escreve como `tasks/x.md`.
23
+ */
24
+ function pastasDeQuemCita(raiz, crew, citadoEm) {
25
+ const agentes = path.resolve(raiz, crew, 'agents') + path.sep;
26
+ return citadoEm.filter((arquivo) => arquivo.startsWith(agentes)).flatMap((arquivo) => {
27
+ const pasta = path.dirname(arquivo);
28
+ const nome = path.basename(arquivo);
29
+ return nome.endsWith(SUFIXO_DE_AGENTE) ? [pasta, path.join(pasta, nome.slice(0, -SUFIXO_DE_AGENTE.length))] : [pasta];
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Caminho real do que foi citado, ou null quando não existe. Ordem: pasta da crew → raiz do
35
+ * projeto → absoluto → pastas ao lado dos arquivos de agente ou de task que citam (`citadoEm`).
36
+ */
37
+ export function resolver(raiz, crew, ref, citadoEm = []) {
38
+ if (ehAbsoluto(ref)) return existsSync(ref) ? path.resolve(ref) : null;
39
+ for (const base of [path.resolve(raiz, crew), raiz, ...pastasDeQuemCita(raiz, crew, citadoEm)]) {
40
+ const p = path.resolve(base, ref);
41
+ if (existsSync(p)) return p;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * Índice nome → caminhos relativos à raiz (pasta termina em `/`), sem saídas, dependências e
48
+ * pastas ocultas. Para ao passar de `limite` itens: aí `parcial` é true.
49
+ */
50
+ export async function indexar(raiz, limite) {
51
+ const porNome = new Map();
52
+ let vistos = 0;
53
+ let parcial = false;
54
+ async function percorrer(dir) {
55
+ let entradas;
56
+ try { entradas = await readdir(dir, { withFileTypes: true }); } catch { return; }
57
+ for (const e of entradas) {
58
+ if (++vistos > limite) { parcial = true; return; }
59
+ if (e.name.startsWith('.') || (e.isDirectory() && IGNORAR.has(e.name))) continue;
60
+ const abs = path.join(dir, e.name);
61
+ const chave = e.name.toLowerCase();
62
+ if (!porNome.has(chave)) porNome.set(chave, []);
63
+ porNome.get(chave).push(barra(path.relative(raiz, abs)) + (e.isDirectory() ? '/' : ''));
64
+ if (e.isDirectory()) await percorrer(abs);
65
+ }
66
+ }
67
+ await percorrer(raiz);
68
+ return { porNome, parcial };
69
+ }
70
+
71
+ /** Candidatos com o mesmo nome. Citado com barra final só casa com pasta; sem ela, com os dois. */
72
+ export function candidatosPorNome(indice, ref) {
73
+ const todos = indice.porNome.get(path.basename(semBarraFinal(ref)).toLowerCase()) ?? [];
74
+ return temBarraFinal(ref) ? todos.filter((c) => c.endsWith('/')) : todos;
75
+ }
76
+
77
+ /** Nomes do que existe na pasta em que o caminho deveria estar. */
78
+ export async function nomesDaPastaEsperada(raiz, crew, ref) {
79
+ const pai = resolver(raiz, crew, path.dirname(semBarraFinal(ref)));
80
+ if (!pai) return [];
81
+ try { return (await readdir(pai)).filter((f) => !f.startsWith('.')); } catch { return []; }
82
+ }