@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.
- package/CHANGELOG.md +76 -0
- package/README.md +19 -5
- package/package.json +1 -1
- package/src/cli.js +2 -1
- package/src/commands/init.js +26 -18
- package/src/lib/migrations.js +40 -3
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/core/prompts/build.prompt.md +2 -0
- package/templates/_opencrew/core/runner.pipeline.md +54 -26
- package/templates/_opencrew/core/scripts/comum.mjs +48 -0
- package/templates/_opencrew/core/scripts/conferir-fontes/busca.mjs +82 -0
- package/templates/_opencrew/core/scripts/conferir-fontes/coleta.mjs +104 -0
- package/templates/_opencrew/core/scripts/conferir-fontes/relatorio.mjs +52 -0
- package/templates/_opencrew/core/scripts/conferir-fontes.mjs +82 -136
- package/templates/_opencrew/core/scripts/verificar/argumentos.mjs +33 -0
- package/templates/_opencrew/core/scripts/verificar/arquivos.mjs +50 -0
- package/templates/_opencrew/core/scripts/verificar/html.mjs +52 -0
- package/templates/_opencrew/core/scripts/verificar/leitura.mjs +138 -71
- package/templates/_opencrew/core/scripts/verificar/medicao.mjs +142 -0
- package/templates/_opencrew/core/scripts/verificar/pecas.mjs +190 -0
- package/templates/_opencrew/core/scripts/verificar/proibicoes.mjs +100 -0
- package/templates/_opencrew/core/scripts/verificar/regras.mjs +142 -92
- package/templates/_opencrew/core/scripts/verificar/relatorio.mjs +43 -0
- package/templates/_opencrew/core/scripts/verificar/secoes.mjs +145 -0
- 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
|
-
>
|
|
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
|
|
233
|
+
Para regravar as pontes de IDE num workspace que já existe:
|
|
228
234
|
|
|
229
235
|
```bash
|
|
230
|
-
npx @aksp/opencrew init --repair-bridges
|
|
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
|
|
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
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
|
|
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
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
26
|
-
if (opts['repair-bridges']
|
|
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
|
|
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();
|
package/src/lib/migrations.js
CHANGED
|
@@ -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.
|
|
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. **
|
|
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
|
|
110
|
-
(
|
|
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
|
|
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
|
-
|
|
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},…"
|
|
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
|
|
689
|
-
|
|
690
|
-
|
|
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
|
|
698
|
-
|
|
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
|
|
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
|
|
709
|
-
|
|
710
|
-
|
|
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
|
+
}
|