@aksp/opencrew 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -3
- package/README.md +5 -2
- package/package.json +1 -1
- package/templates/AGENTS.md +8 -3
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/core/best-practices/_catalog.yaml +5 -0
- package/templates/_opencrew/core/best-practices/texto-livre.md +23 -0
- package/templates/_opencrew/core/formato-da-crew.md +1 -1
- package/templates/_opencrew/core/prompts/build.prompt.md +3 -3
- package/templates/_opencrew/core/prompts/design.prompt.md +5 -5
- package/templates/_opencrew/core/prompts/discovery.prompt.md +8 -6
- package/templates/_opencrew/core/prompts/documento.prompt.md +9 -5
- package/templates/_opencrew/core/prompts/entrega.prompt.md +7 -2
- package/templates/_opencrew/core/prompts/repair.prompt.md +1 -1
- package/templates/_opencrew/core/runner.pipeline.md +17 -17
- package/templates/_opencrew/core/scripts/caminho/argumentos.mjs +3 -1
- package/templates/_opencrew/core/scripts/caminho/nucleo.mjs +16 -0
- package/templates/_opencrew/core/scripts/caminho.mjs +11 -8
- package/templates/_opencrew/core/scripts/conferir-fontes/relatorio.mjs +4 -2
- package/templates/_opencrew/core/scripts/conferir-fontes.mjs +22 -16
- package/templates/_opencrew/core/scripts/conserto/achados.mjs +1 -1
- package/templates/_opencrew/core/scripts/documento/markdown.mjs +2 -1
- package/templates/_opencrew/core/scripts/documento/pacote.mjs +1 -0
- package/templates/_opencrew/core/scripts/documento/perfil.mjs +14 -0
- package/templates/_opencrew/core/scripts/entrega/pendencias.mjs +4 -1
- package/templates/_opencrew/core/scripts/entrega/separar.mjs +2 -1
- package/templates/_opencrew/core/scripts/verificar/documento.mjs +20 -0
- package/templates/_opencrew/core/scripts/verificar/medicao.mjs +2 -1
- package/templates/_opencrew/core/scripts/verificar.mjs +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,21 +3,67 @@
|
|
|
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.12.0] — 2026-10-07
|
|
7
|
+
|
|
8
|
+
Fase U5, fatia 1: "Polimento do uso" (`specs/fase-u5a-polimento-do-uso.md`). A U5 é a fase de
|
|
9
|
+
fechamento do projeto, em quatro fatias (`specs/fase-u5-roteiro.md`). Chega a quem já usa com um
|
|
10
|
+
`npx @aksp/opencrew@latest update`.
|
|
11
|
+
|
|
12
|
+
Ainda não nesta versão: o runner dividido (1.13.0), o histórico confiável e o "retomar" (1.14.0)
|
|
13
|
+
e o pedido avulso à crew (1.15.0).
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Formato `texto-livre`** (o 24º guia): para o texto que não é post nem documento para
|
|
17
|
+
imprimir ou assinar — proposta, minuta que vira HTML ou PDF, plano, relatório interno. Não tem
|
|
18
|
+
limite de tamanho, não gera Word, e a entrega leva o arquivo inteiro para `outros/`. O
|
|
19
|
+
`/opencrew repair` passa a propô-lo para esse tipo de passo.
|
|
20
|
+
- **O que o Word não vai converter aparece antes do revisor.** No passo com
|
|
21
|
+
`format: documento-oficial`, o verificador avisa de imagem, de marcação `:::` desconhecida e
|
|
22
|
+
de bloco de assinaturas sem fim. São alertas: quem decide é o revisor.
|
|
23
|
+
- Aviso de conversão quando uma linha de tabela tem mais células que o cabeçalho.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **O nome da execução (`run_id`) vem do script**, com a data e a hora do computador
|
|
27
|
+
(`caminho.mjs <crew> pasta`, sem `--run`). Antes a IA montava a hora por conta própria.
|
|
28
|
+
- **Conferência de fontes:** o resumo concorda em número ("1 fonte", "1 alerta"); quando há
|
|
29
|
+
caminho com sugestão, o relatório diz como corrigir; com `--corrigir`, cada arquivo alterado
|
|
30
|
+
aparece com o nome da cópia que ficou, e o relatório sai uma vez só.
|
|
31
|
+
- Depois de um conserto por veto, o runner confere o arquivo de novo.
|
|
32
|
+
- O runner não cita mais ferramenta de uma IDE só ("Task tool", `.claude/settings.local.json`).
|
|
33
|
+
- **Perfil de documento oficial:** chave escrita quase certa (`Logotipo:`, `rodapé:`, com
|
|
34
|
+
espaço antes) agora é erro que diz a grafia certa; antes a linha era ignorada em silêncio.
|
|
35
|
+
- O onboarding grava o `company.md` com seis cabeçalhos fixos e tira a marca
|
|
36
|
+
`NOT CONFIGURED` dos dois arquivos.
|
|
37
|
+
- A linha "Não medido" some para formato que não declara limite (`documento-oficial`,
|
|
38
|
+
`texto-livre`).
|
|
39
|
+
- Na entrega sem canal de rede, "O que não foi conferido" não cita imagens nem redes.
|
|
40
|
+
- Prompts de criação: dez contradições que a execução real da 1.11.0 achou ganharam uma frase
|
|
41
|
+
cada (a pergunta de tier com modelo, `extends:` × "do zero", dependências de agente lidas
|
|
42
|
+
também do "Context Loading", skills nativas fora da conferência, exemplo de saída com 15
|
|
43
|
+
linhas, a pergunta 1 quando o comando já traz a descrição, entre outras).
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- **Uma frase errada da 1.11.0.** O `/opencrew repair`, o README e este arquivo diziam que texto
|
|
47
|
+
sem formato "é medido como post de blog". Isso só acontece quando o arquivo tem `title:` no
|
|
48
|
+
frontmatter. O que acontece sempre, sem o formato: o redator não recebe o guia do tipo de
|
|
49
|
+
texto, e o verificador procura no arquivo peças de rede (legenda, post). A frase foi trocada
|
|
50
|
+
em todos os lugares.
|
|
51
|
+
|
|
6
52
|
## [1.11.0] — 2026-10-07
|
|
7
53
|
|
|
8
54
|
Fase U4, fatia 1: "Conserto de crews e caminho de criação" (`specs/fase-u4a-conserto-de-crews.md`).
|
|
9
55
|
Chega a quem já usa com um `npx @aksp/opencrew@latest update`; as suas crews só mudam quando você
|
|
10
56
|
pede o conserto e diz sim a cada ponto.
|
|
11
57
|
|
|
12
|
-
Ainda não nesta versão: histórico confiável e pedido avulso à
|
|
13
|
-
|
|
58
|
+
Ainda não nesta versão: histórico confiável, retomar uma execução interrompida e pedido avulso à
|
|
59
|
+
crew (`/opencrew pedir`), que ficaram para a fase U5 (`specs/fase-u5-roteiro.md`). O conserto não reordena passos: crew
|
|
14
60
|
sem revisão, sem aprovação final ou que publica antes da revisão é apontada e resolvida com
|
|
15
61
|
`/opencrew edit`.
|
|
16
62
|
|
|
17
63
|
### Added
|
|
18
64
|
- **`/opencrew repair <crew>` conserta crews antigas.** Ele lê a crew e mostra, em português, o
|
|
19
65
|
que falta para as melhorias das versões seguintes valerem nela: passo sem o formato do texto
|
|
20
|
-
(o
|
|
66
|
+
(o redator ficava sem o guia do tipo de texto), crew sem os arquivos do projeto que deve ler,
|
|
21
67
|
proibição sem trecho entre aspas (que o verificador não consegue barrar), nomes dos agentes,
|
|
22
68
|
passo que publica sem a marca. Conserta um ponto por vez, com o seu sim; cada arquivo alterado
|
|
23
69
|
ganha uma cópia `.bak`. Quem grava é um script (`_opencrew/core/scripts/conserto.mjs`), não a
|
package/README.md
CHANGED
|
@@ -185,7 +185,7 @@ meu-projeto/
|
|
|
185
185
|
│ │ ├── skills.engine.md ← gerenciador de skills
|
|
186
186
|
│ │ ├── architect.agent.yaml ← definição do Arquiteto
|
|
187
187
|
│ │ ├── formato-da-crew.md ← o formato dos arquivos de uma crew (crew.yaml, pipeline.yaml, passos)
|
|
188
|
-
│ │ ├── best-practices/ ←
|
|
188
|
+
│ │ ├── best-practices/ ← 24 guias de melhores práticas + _catalog.yaml
|
|
189
189
|
│ │ ├── scripts/ ← verificador, conferência de fontes, caminhos, entrega, documento Word, conserto de crews e os scripts do Escritório
|
|
190
190
|
│ │ ├── modelos/ ← modelo do perfil de documento oficial (papel timbrado)
|
|
191
191
|
│ │ ├── escritorio/ ← página do Escritório ao vivo (abre com /opencrew dashboard)
|
|
@@ -439,12 +439,15 @@ o que você fez:
|
|
|
439
439
|
um bloco completo no fim. O bloco começa por um comentário que diz que ele é do OpenCrew: as
|
|
440
440
|
suas linhas ficam fora dele.
|
|
441
441
|
- **E as crews que você já tinha?** O `update` não mexe nelas. Para levar a elas o que veio
|
|
442
|
-
depois — o formato de cada texto (sem ele o
|
|
442
|
+
depois — o formato de cada texto (sem ele o redator não recebe o guia do tipo de texto), os
|
|
443
443
|
arquivos do projeto que a crew deve ler, as proibições que o verificador consegue barrar —,
|
|
444
444
|
peça na sua IDE `/opencrew repair <nome>`. Ele mostra o que falta, pergunta antes de cada
|
|
445
445
|
mudança e deixa uma cópia `.bak` do arquivo que alterou. O que não dá para consertar assim
|
|
446
446
|
(crew sem passo de revisão, publicação antes da revisão) ele aponta e manda para
|
|
447
447
|
`/opencrew edit`.
|
|
448
|
+
- **Texto que não é post nem documento para assinar?** Proposta, minuta que outro passo diagrama,
|
|
449
|
+
plano: o passo recebe o formato `texto-livre`. Não há limite de tamanho, nada vira Word, e a
|
|
450
|
+
entrega guarda o arquivo como está, em `outros/`.
|
|
448
451
|
- **A instalação anterior parou no meio?** O `update` não altera nada e pede para você rodar
|
|
449
452
|
`npx @aksp/opencrew init`, que conclui a instalação sem apagar o que já existe.
|
|
450
453
|
- **Apagou um modelo de crew ou um skill do catálogo?** Ele volta no `update`, e a saída diz o
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -12,7 +12,8 @@ On activation, perform these steps IN ORDER:
|
|
|
12
12
|
3. Check if company.md is empty or contains only the template — if so, trigger ONBOARDING
|
|
13
13
|
(except for `/opencrew documento` and the request to deliver a run that already ended: neither
|
|
14
14
|
uses the company context)
|
|
15
|
-
4. Otherwise
|
|
15
|
+
4. Otherwise: when the message carried a command or a request, route it (Command Routing);
|
|
16
|
+
display the MAIN MENU only when it carried none
|
|
16
17
|
|
|
17
18
|
## Onboarding Flow (first time only)
|
|
18
19
|
|
|
@@ -25,8 +26,12 @@ If `company.md` is empty or contains `<!-- NOT CONFIGURED -->`:
|
|
|
25
26
|
description/sector, target audience, products/services, tone of voice,
|
|
26
27
|
social media profiles
|
|
27
28
|
4. Present findings in a clean summary, ask the user to confirm or correct,
|
|
28
|
-
save the profile to `_opencrew/_memory/company.md`
|
|
29
|
-
|
|
29
|
+
save the profile to `_opencrew/_memory/company.md` under these fixed headings, in this order:
|
|
30
|
+
`## Nome`, `## O que faz`, `## Público`, `## Produtos e serviços`, `## Tom de voz`,
|
|
31
|
+
`## Site e redes` (under it, the site goes on its own line, `- Site: https://…`). Then remove the line
|
|
32
|
+
`<!-- NOT CONFIGURED -->` from `company.md` and from `preferences.md`
|
|
33
|
+
5. When the message that started this carried a command or a request, route it now (Command
|
|
34
|
+
Routing); show the main menu only when it carried none
|
|
30
35
|
|
|
31
36
|
## Main Menu
|
|
32
37
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.12.0
|
|
@@ -119,3 +119,8 @@ catalog:
|
|
|
119
119
|
name: "Documento oficial (Word)"
|
|
120
120
|
whenToUse: "Creating agents that produce a document to print, sign or file (minutes, official letter, statement, contract, formal report) that becomes a Word file."
|
|
121
121
|
file: documento-oficial.md
|
|
122
|
+
|
|
123
|
+
- id: texto-livre
|
|
124
|
+
name: "Texto livre"
|
|
125
|
+
whenToUse: "Creating agents that write a text with no channel and no Word file: commercial proposal, draft that becomes HTML or PDF, work plan, internal report."
|
|
126
|
+
file: texto-livre.md
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Texto livre"
|
|
3
|
+
content_type: "plain-text"
|
|
4
|
+
description: "Text with no channel and no Word file: a proposal, a draft that becomes HTML or PDF, a plan, an internal report"
|
|
5
|
+
whenToUse: |
|
|
6
|
+
Creating agents that write a text that is not a post for a network and is not a document to print or sign: commercial proposal, draft (minuta) that another step turns into HTML or PDF, work plan, internal report, briefing.
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Compact Rules
|
|
11
|
+
|
|
12
|
+
1. Write the full, final text: no outline, no notes to the reader, no "insert here".
|
|
13
|
+
2. No YAML frontmatter and no labels such as `=== TITLE ===`: everything in the file is the text itself.
|
|
14
|
+
3. Use `#`, `##` and `###` for the sections, simple tables for figures, `- ` for lists.
|
|
15
|
+
4. Missing real data is marked `[PREENCHER: o que falta]`; never invent a name, a date, a price or a number.
|
|
16
|
+
5. Copy figures exactly from the file they come from; when a total is shown, the parts add up to it.
|
|
17
|
+
6. Follow the structure the step asks for (its "Output Format"): this guide adds no structure of its own.
|
|
18
|
+
|
|
19
|
+
## What this format does not do
|
|
20
|
+
|
|
21
|
+
There is no size limit here and nothing is converted: the delivery keeps the file as it is, in
|
|
22
|
+
`outros/`. For a text to print, sign or file, use `documento-oficial`; for a post, an e-mail or a
|
|
23
|
+
message, use the format of that channel.
|
|
@@ -132,7 +132,7 @@ outputFile: crews/atas-do-conselho/output/pauta.md # optional: the user's answ
|
|
|
132
132
|
|---|---|
|
|
133
133
|
| `execution` | `subagent` runs in the background; `inline` runs in the conversation. A step that publishes or sends is always `inline` |
|
|
134
134
|
| `agent` | the agent id |
|
|
135
|
-
| `format` | every step whose text is checked before the review (from the `on_reject` step up to the review) declares one; without it the
|
|
135
|
+
| `format` | every step whose text is checked before the review (from the `on_reject` step up to the review) declares one; without it the writer does not get the guide of that kind of text and the checker looks in the file for pieces of a network (caption, post, blog title). A text to print, sign or file is `documento-oficial`; a text with no channel and no Word file (a proposal, a draft that becomes HTML or PDF, a plan) is `texto-livre`. Omit only for research, analysis and the review itself |
|
|
136
136
|
| `inputFile`, `outputFile` | always under `crews/{code}/output/`; never `pipeline/data/` |
|
|
137
137
|
| `model_tier` | `fast` or `powerful`, only on `subagent` steps. Express: `fast`. Standard: `fast` for research and data gathering, `powerful` for the rest. Full: `powerful`. Inline steps do not carry it |
|
|
138
138
|
| `side_effects: irreversible` | every step that publishes, posts or sends outside the project; such steps come after the review and the final approval |
|
|
@@ -87,7 +87,7 @@ each. Write every file in that format; what follows here only adds what is speci
|
|
|
87
87
|
prose of step files and tasks, between backticks, e.g. `` `Ativos/Identidade Visual/logo.png` ``. NEVER write absolute paths
|
|
88
88
|
(`C:/…`, `J:/…`, `/Users/…`): they break as soon as the user moves or syncs the folder.
|
|
89
89
|
- `agent_dependencies:` — derive the entries from the step order: for each agent step, list
|
|
90
|
-
the agent(s) whose output it reads via `inputFile
|
|
90
|
+
the agent(s) whose output it reads, via `inputFile` or in its "Context Loading" list.
|
|
91
91
|
|
|
92
92
|
2. **`crews/{code}/crew-party.csv`** — Agent manifest
|
|
93
93
|
- The header row MUST be EXACTLY these columns, in this order:
|
|
@@ -450,7 +450,7 @@ that the agent knows exactly what to produce.]
|
|
|
450
450
|
|
|
451
451
|
[A COMPLETE, realistic example of what this step should produce.
|
|
452
452
|
This is not a template — it's a fully realized output with realistic content.
|
|
453
|
-
Must be
|
|
453
|
+
Must be 15+ lines and demonstrate the expected quality, depth, and formatting.
|
|
454
454
|
The agent uses this as a reference for what "good" looks like.]
|
|
455
455
|
|
|
456
456
|
## Veto Conditions
|
|
@@ -607,7 +607,7 @@ Additional programmatic checks — read the filesystem to verify:
|
|
|
607
607
|
- [ ] Every `agent_dependencies` key and value references a real agent `id` from crew-party.csv
|
|
608
608
|
- [ ] All task files referenced in agent frontmatter exist
|
|
609
609
|
- [ ] All step files referenced in `pipeline.yaml` exist
|
|
610
|
-
- [ ] Skills listed in `crew.yaml` are installed in `skills/`
|
|
610
|
+
- [ ] Skills listed in `crew.yaml` are installed in `skills/` (the native ones, `web_search` and `web_fetch`, need no folder)
|
|
611
611
|
- [ ] Best-practices files referenced by `format:` fields in steps exist in `_opencrew/core/best-practices/`
|
|
612
612
|
|
|
613
613
|
---
|
|
@@ -81,7 +81,7 @@ Compile all research into a structured research brief document. This will feed P
|
|
|
81
81
|
|
|
82
82
|
After research completes, determine the crew's tier:
|
|
83
83
|
|
|
84
|
-
1. If a template was used (
|
|
84
|
+
1. If a template was used (`discovery.yaml` has the `template:` field, which the Discovery writes only in that case) → use the `tier` recorded there.
|
|
85
85
|
2. Otherwise, read the default tier from `_opencrew/_memory/preferences.md` → `Default Tier` field.
|
|
86
86
|
3. If neither is set, default to `standard`.
|
|
87
87
|
|
|
@@ -182,7 +182,7 @@ From the crew's purpose and domains (in `discovery.yaml`), identify what human r
|
|
|
182
182
|
| Domain / Need | Possible Roles |
|
|
183
183
|
|---|---|
|
|
184
184
|
| Research, fact-finding, market analysis | 🔎 Pesquisador — finds trends, maps keywords, does market research |
|
|
185
|
-
| Writing, copy, content creation | ✍️ Redator — writes strategic text based on research, including captions and hooks |
|
|
185
|
+
| Writing, copy, content creation, documents | ✍️ Redator — writes strategic text based on research, including captions and hooks; in a document crew, writes the minutes, the letter, the contract or the proposal |
|
|
186
186
|
| Strategy, positioning, planning | 🧠 Estrategista — defines angles, editorial calendar, competitive positioning |
|
|
187
187
|
| Visual design, image creation | 🎨 Designer — creates visual content aligned with brand identity |
|
|
188
188
|
| Quality review, accuracy check | 🔍 Revisor — validates quality, tone, accuracy against criteria |
|
|
@@ -244,7 +244,7 @@ For each approved role, consult this mapping to determine which skills and best-
|
|
|
244
244
|
| Role | Typical Skills | Typical Best-Practices |
|
|
245
245
|
|------|---------------|----------------------|
|
|
246
246
|
| Pesquisador (Researcher) | `web_search`, `web_fetch` (native) | `researching.md` |
|
|
247
|
-
| Redator (Writer/Copywriter) | `web_search` (native, for fact-checking) | `copywriting.md` + platform-specific format file |
|
|
247
|
+
| Redator (Writer/Copywriter) | `web_search` (native, for fact-checking) | `copywriting.md` + platform-specific format file; for a text to print, sign or file: `documento-oficial.md`; for a text with no channel and no Word file (proposal, draft that becomes HTML or PDF, plan): `texto-livre.md` |
|
|
248
248
|
| Estrategista (Strategist) | `web_search` (native) | `strategist.md` |
|
|
249
249
|
| Designer (Visual Designer) | `image-creator`, `image-ai-generator`, `canva` | `image-design.md` |
|
|
250
250
|
| Revisor (Reviewer) | None required | `review.md` |
|
|
@@ -342,7 +342,7 @@ Based on discovery answers + company context + research findings + extracted art
|
|
|
342
342
|
|
|
343
343
|
Before designing any agent from scratch, check the shared registry at `_opencrew/agents/`:
|
|
344
344
|
|
|
345
|
-
1. List
|
|
345
|
+
1. List the files of `_opencrew/agents/` (with your folder-listing tool, no command) — each `.agent.md` file is a reusable base agent
|
|
346
346
|
2. For each role approved in Phase D, check if a matching base agent exists:
|
|
347
347
|
- Pesquisador → `_opencrew/agents/researcher.agent.md`
|
|
348
348
|
- Redator → `_opencrew/agents/copywriter.agent.md`
|
|
@@ -380,7 +380,7 @@ Guidelines:
|
|
|
380
380
|
|
|
381
381
|
Design the crew with appropriate agents:
|
|
382
382
|
- Follow the deep `.agent.md` format with full sections: Persona (Role, Identity, Communication Style), Principles, Operational Framework, Voice Guidance, Output Examples, Anti-Patterns, Quality Criteria, Integration
|
|
383
|
-
-
|
|
383
|
+
- Use `extends:` when the Shared Agent Registry Check found a base agent that fits; design from scratch only the agents with no base, informed by the relevant best-practices files read in Phase A
|
|
384
384
|
- Each agent has exactly one clear responsibility
|
|
385
385
|
- Every crew needs a review step for quality control (a reviewer agent in Standard and Full)
|
|
386
386
|
- YAGNI — never create agents that aren't strictly necessary
|
|
@@ -7,7 +7,7 @@ You are a strategic systems thinker and patient crew architect. You help users a
|
|
|
7
7
|
## Communication Style
|
|
8
8
|
|
|
9
9
|
- One question at a time — never present two questions in the same message
|
|
10
|
-
- Use numbered lists whenever options are available
|
|
10
|
+
- Use numbered lists whenever options are available (the user knows what to do: never add "reply with a number")
|
|
11
11
|
- Adapt follow-up questions based on what the user says, not a fixed script
|
|
12
12
|
- Confirm understanding before moving to the next topic
|
|
13
13
|
- Maximum 8 questions total across the entire discovery flow
|
|
@@ -70,7 +70,7 @@ If exactly 1 template exists, still offer option 1 ("Começar do zero") as a sec
|
|
|
70
70
|
Ask:
|
|
71
71
|
> "What do you want this crew to do? Describe the end result you want."
|
|
72
72
|
|
|
73
|
-
This is always the first question. Accept any answer — a sentence, a paragraph, bullet points. Do NOT assume any domain. Do NOT suggest options at this stage.
|
|
73
|
+
This is always the first question — except when the command already carried the description (`/opencrew create <description>`): then do not ask it again; repeat the description in one sentence and ask only "É isso? Quer acrescentar alguma coisa?". Accept any answer — a sentence, a paragraph, bullet points. Do NOT assume any domain. Do NOT suggest options at this stage.
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -95,7 +95,7 @@ Save the detected domain as `domain`.
|
|
|
95
95
|
|
|
96
96
|
### Step 3 — Context Exploration (adaptive, ONE question at a time)
|
|
97
97
|
|
|
98
|
-
Based on the detected domain, ask the most relevant contextual question first. Wait for the answer before asking the next one. Ask at most
|
|
98
|
+
Based on the detected domain, ask the most relevant contextual question first. Wait for the answer before asking the next one. Ask at most 3 questions in this step, not counting the project sources question below, which is always asked.
|
|
99
99
|
|
|
100
100
|
**If domain = `content`:**
|
|
101
101
|
1. Who is this content for? (multiple choice: current customers / potential leads / general audience / other)
|
|
@@ -105,7 +105,7 @@ Based on the detected domain, ask the most relevant contextual question first. W
|
|
|
105
105
|
**If domain = `document`:**
|
|
106
106
|
1. Which documents should the crew produce? (open-ended: ata, ofício, contrato, proposta…)
|
|
107
107
|
2. Who signs each document, and who receives it? (open-ended)
|
|
108
|
-
3.
|
|
108
|
+
3. Must the text become a Word file, to print, sign or file? (yes / no — "no" is the proposal or the draft that is read in the chat and laid out by hand later: nothing is converted. Only on a "yes", ask in the same message whether the organization has letterhead — logo, header lines, footer; the letterhead itself is set up later, the first time a Word document is generated)
|
|
109
109
|
|
|
110
110
|
The project sources question below matters most here: ask which files of the project rule the text (bylaws, previous minutes, price table, contract template).
|
|
111
111
|
|
|
@@ -206,7 +206,7 @@ Set `investigation.enabled: false` and continue.
|
|
|
206
206
|
|
|
207
207
|
### Step 6 — Target Formats (content crews ONLY)
|
|
208
208
|
|
|
209
|
-
If domain = `document`, do not ask: save `target_formats: ["documento-oficial"]
|
|
209
|
+
If domain = `document`, do not ask: save `target_formats: ["documento-oficial"]` when the text must become a Word file (question 3 of Step 3), or `["texto-livre"]` when it must not. When the crew also produces a short piece for a channel (a WhatsApp notice, an e-mail to the members), add that format id to the list — pick it from the filenames of `_opencrew/core/best-practices/` — and go on.
|
|
210
210
|
Skip this step entirely for the other non-content domains.
|
|
211
211
|
|
|
212
212
|
If domain = `content`, ask:
|
|
@@ -268,6 +268,7 @@ project_sources: # relative to the project root; becomes `fo
|
|
|
268
268
|
purpose: "{what the crew uses it for}"
|
|
269
269
|
purpose: "{user's description from Step 1}"
|
|
270
270
|
domain: "{document | content | research | automation | analysis | mixed}"
|
|
271
|
+
template: "{template folder name}" # ONLY when a template was chosen in Step 0; omit the line otherwise
|
|
271
272
|
# When a template was used (Step 0), these fields are populated from discovery.template.yaml:
|
|
272
273
|
domains: [] # list of domain tags from template (e.g., [content-marketing, seo])
|
|
273
274
|
tier: "standard" # from template or preferences Default Tier
|
|
@@ -289,7 +290,8 @@ context:
|
|
|
289
290
|
documents: "{answer from Step 3}"
|
|
290
291
|
signer: "{who signs}"
|
|
291
292
|
recipients: "{who receives}"
|
|
292
|
-
|
|
293
|
+
word_file: "{yes | no}"
|
|
294
|
+
letterhead: "{yes | no | not sure — only when word_file is yes}"
|
|
293
295
|
# For content crews:
|
|
294
296
|
audience: "{answer from Step 3}"
|
|
295
297
|
platforms: "{answer from Step 3}"
|
|
@@ -37,10 +37,12 @@ Este projeto ainda não tem papel timbrado configurado. Quer configurar agora (l
|
|
|
37
37
|
1. Run, from the project root: `node _opencrew/core/scripts/documento.mjs --criar-perfil`
|
|
38
38
|
Its last line is `PERFIL:CRIADO` (the file was created from the model) or `PERFIL:JA-EXISTE`
|
|
39
39
|
(it was already there and was not touched).
|
|
40
|
-
2. Ask the user
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
2. Ask the user three questions, one at a time, with these words (the logo is a PNG file of up
|
|
41
|
+
to 2 MB inside the project, given by its path from the root):
|
|
42
|
+
- "Qual é o arquivo do logotipo? (PNG, dentro do projeto. Pode responder 'sem logotipo'.)"
|
|
43
|
+
- "Quais são as linhas do cabeçalho? Até três: nome da entidade, CNPJ ou registro, endereço."
|
|
44
|
+
- "Quer um texto no rodapé, além de 'Página X de Y'?"
|
|
45
|
+
Any of them may stay empty: what is empty does not appear in the document.
|
|
44
46
|
3. Fill the file `_opencrew/_memory/documento-oficial.md` with the answers: write only the value
|
|
45
47
|
after the colon of `logotipo`, `cabecalho_1`, `cabecalho_2`, `cabecalho_3` and `rodape`, each
|
|
46
48
|
exactly as the user gave it. Leave every other line as it is (comments, margins, font).
|
|
@@ -103,7 +105,9 @@ one message in PT-BR and stops, with nothing written). Show the message to the u
|
|
|
103
105
|
|
|
104
106
|
- **The message points to something in the command you wrote** (an option, a path, more than one
|
|
105
107
|
file): fix the command and run it once more.
|
|
106
|
-
- **
|
|
108
|
+
- **"Não encontrei {arquivo}." or "Só converto texto…"**: the file is not there, or it is not a
|
|
109
|
+
`.md` or `.txt`. Show the message and go back to the question of Step 1.
|
|
110
|
+
- **The message starts with "Perfil, linha {n}:"** (an unknown or misspelled key, a value out of range, a logo
|
|
107
111
|
that is missing, is not a PNG or is over 2 MB): the document is not generated with a wrong
|
|
108
112
|
letterhead. Show the message, ask the user for the right value of that line, write it in the
|
|
109
113
|
profile and run again. Do not switch to `--sem-perfil` by yourself.
|
|
@@ -69,7 +69,8 @@ without the `ENTREGA:` line. Do not rewrite it and do not add files it does not
|
|
|
69
69
|
the report of the check made at delivery time (it is not part of the delivery).
|
|
70
70
|
|
|
71
71
|
- `ENTREGA:OK` → go on with the run (Step 5 first, when it applies). When the summary has a line
|
|
72
|
-
|
|
72
|
+
that starts with "Sem papel timbrado:", the Word documents came out with no letterhead: say so
|
|
73
|
+
and offer to set it up (`/opencrew documento`); after the profile exists, deliver again.
|
|
73
74
|
- `ENTREGA:COM_RESSALVA` → everything that was missing is a ressalva the user accepted: the
|
|
74
75
|
`LEIA-ME.md` opens with it and the channel is "Pronto, com ressalva"; go on as with `ENTREGA:OK`.
|
|
75
76
|
- `ENTREGA:INCOMPLETA` → a channel is not ready, the destination was refused or a file could not be
|
|
@@ -103,7 +104,11 @@ the report of the check made at delivery time (it is not part of the delivery).
|
|
|
103
104
|
"Não consegui gravar …"): show the message as it came and ask for another folder (Step 5, with
|
|
104
105
|
the new answer) or for a new attempt, which is the same command again. A file of the list that
|
|
105
106
|
does not exist: ask for it, or take it out of the list.
|
|
106
|
-
- **A Word document that was not generated** (the line
|
|
107
|
+
- **A Word document that was not generated** (the line "Não consegui gerar o Word de
|
|
108
|
+
{arquivo}: …"): show the message as it came; this is never accepted with option 2. When the
|
|
109
|
+
reason starts with "Perfil, linha {n}:", the letterhead profile has a wrong line: ask the user
|
|
110
|
+
for the right value, write it in `_opencrew/_memory/documento-oficial.md` and run the same
|
|
111
|
+
command again.
|
|
107
112
|
The first call never has `--aceitar-pendencias`. Outside option 2 it goes only when the user
|
|
108
113
|
already chose "Aceitar assim mesmo" in the review loop of this run and what is missing is only
|
|
109
114
|
what was accepted there: then run the command again with it, without asking.
|
|
@@ -52,7 +52,7 @@ codes between brackets, the `--aplicar` lines or the `CONSERTO:` status line.
|
|
|
52
52
|
|---|---|---|
|
|
53
53
|
| `nome-de-agente` | The agent has no two-word persona name. Propose one by the Agent Naming Convention of `_opencrew/core/prompts/design.prompt.md` (two words with the same initial, a different initial for each agent of the crew), keeping the first name the agent already has, and ask: "O agente {id} está sem nome de pessoa. Proponho {Nome Sobrenome}. Posso gravar?" | `--aplicar "nome:{id}={Nome Sobrenome}"`; the names reach the list of the crew with `manifesto`, below — when `manifesto` is not among the findings, run it right after this one |
|
|
54
54
|
| `manifesto` | "O arquivo de nomes da crew está incompleto; por isso aparece a função no lugar do nome. Posso refazer a partir dos arquivos dos agentes?" | `--aplicar "manifesto"` |
|
|
55
|
-
| `formato` | Read each listed step and propose one format per step, among the files of `_opencrew/core/best-practices/` (and `_opencrew/best-practices.local/`). A text to print, sign or file — ata, ofício, contrato,
|
|
55
|
+
| `formato` | Read each listed step and propose one format per step, among the files of `_opencrew/core/best-practices/` (and `_opencrew/best-practices.local/`). A text that must become a Word file to print, sign or file — ata, ofício, contrato, parecer — gets `documento-oficial`; a text with no channel that does not become a Word file (a plan, an internal report, a proposal or a draft that becomes HTML or PDF) gets `texto-livre`. When you cannot tell which of the two it is (a proposta, a minuta), ask first: "O texto do passo {n} precisa virar um arquivo Word para imprimir ou assinar?" — sim: `documento-oficial`; não: `texto-livre`. When the user refuses your proposal and says what the text is, propose once more with the right format. Then: "Estes passos não dizem que tipo de texto produzem; sem isso, o redator não recebe o guia do tipo de texto e o verificador procura no arquivo peças de rede. Minha proposta: {passo → formato}. Posso gravar assim?" | one `--aplicar "formato:{passo}={formato}"` per step |
|
|
56
56
|
| `fontes` | "Esta crew não registra os arquivos do projeto que ela deve ler antes de escrever. Quais arquivos ou pastas ela precisa conhecer? (Pode responder 'nenhum'.)" Confirm that each one exists (search the project when only a name was given) and ask what the crew uses it for | one `--aplicar "fonte:{caminho}={para que}"` per file or folder, the path relative to the project root |
|
|
57
57
|
| `proibicao` | For each listed item: "Esta proibição não tem um trecho entre aspas, então o verificador não consegue barrar: «{item}». Qual trecho exato devo barrar? Se for uma regra de conteúdo, e não uma palavra ou expressão, responda 'revisão humana': ela fica para o revisor." The excerpt must be words of the item itself | `--aplicar "proibicao:{n}={trecho}"` or `--aplicar "proibicao:{n}=revisao-humana"` |
|
|
58
58
|
| `irreversivel` | For each listed step: "O passo {n} é feito por um agente que tem uma ferramenta de publicar ou enviar ({skill}). Este passo publica ou envia alguma coisa para fora do projeto? Se sim, marco o passo para que ele nunca seja repetido sozinho." | `--aplicar "irreversivel:{n}"` only for a yes |
|
|
@@ -121,7 +121,7 @@ Before starting execution:
|
|
|
121
121
|
- If yes → read `_opencrew/core/skills.engine.md`, follow Operation 2 (Install)
|
|
122
122
|
- If no → **ERROR**: stop pipeline
|
|
123
123
|
b. Read SKILL.md, parse frontmatter for type
|
|
124
|
-
c. If type: mcp, verify MCP is configured in `.
|
|
124
|
+
c. If type: mcp, verify the MCP server is configured in your IDE's MCP configuration (the project's `.mcp.json`, or the file your IDE uses)
|
|
125
125
|
- If missing → **ERROR**: "Skill '{skill}' MCP not configured. Reinstall the skill."
|
|
126
126
|
All skills must resolve successfully before the pipeline starts (fail fast).
|
|
127
127
|
4. **Model tiers**: a `subagent` step declares its own `model_tier` (`fast` or `powerful`), set at crew creation by the crew's tier; inline steps carry none.
|
|
@@ -217,11 +217,11 @@ Before starting execution:
|
|
|
217
217
|
```
|
|
218
218
|
When the selection step was skipped (no `agent_dependencies:` in crew.yaml), this is
|
|
219
219
|
identical to today: all agents listed, no Skipped line.
|
|
220
|
-
5b. **Initialize run folder**:
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
-
|
|
220
|
+
5b. **Initialize run folder**: the script names the run — never build the date or the time yourself:
|
|
221
|
+
- Run the `pasta` command (see "Output Path Transformation" below). It creates the folder of this run and answers `CAMINHO:OK crews/{name}/output/{run_id}`
|
|
222
|
+
- The `run_id` is the last segment of that path: `YYYY-MM-DD-HHmmss` from the computer's clock (e.g. `2026-03-03-143022`; `-2`, `-3` when that folder already exists)
|
|
223
|
+
- The date of this run, wherever one is asked below, is the first 10 characters of the `run_id`
|
|
224
|
+
- Never create a folder by command yourself
|
|
225
225
|
- Store `run_id` in working memory for this run — it will be used for ALL output paths
|
|
226
226
|
6. **Escritório** — if it is on, run `iniciar`, then one `pular` per deselected agent, one after the other (see "Escritório" below).
|
|
227
227
|
|
|
@@ -230,7 +230,7 @@ Before starting execution:
|
|
|
230
230
|
A local page that shows the crew at work, off by default. Follow this section only when the
|
|
231
231
|
already-loaded `preferences.md` has `Dashboard: enabled` (written `- **Dashboard:** enabled` or
|
|
232
232
|
plain `Dashboard: enabled`, any letter case); otherwise run none of these commands. When it is on,
|
|
233
|
-
run
|
|
233
|
+
run, from the project root, the one-line command of each moment:
|
|
234
234
|
|
|
235
235
|
| Moment | Command |
|
|
236
236
|
|---|---|
|
|
@@ -448,7 +448,7 @@ root the one-line command of each moment and read the last line (`CAMINHO:OK {pa
|
|
|
448
448
|
|
|
449
449
|
| Moment | Command |
|
|
450
450
|
|---|---|
|
|
451
|
-
| Start of the run (Initialization, step 5b) | `node _opencrew/core/scripts/caminho.mjs "{name}" pasta
|
|
451
|
+
| Start of the run (Initialization, step 5b) | `node _opencrew/core/scripts/caminho.mjs "{name}" pasta` (no `--run`: the script creates the `run_id`) |
|
|
452
452
|
| Before a step, for its `inputFile` | `node _opencrew/core/scripts/caminho.mjs "{name}" entrada --run "{run_id}" --arquivo "{inputFile}"` |
|
|
453
453
|
| Before a step writes, for the first `outputFile` of each group | `node _opencrew/core/scripts/caminho.mjs "{name}" saida --run "{run_id}" --arquivo "{outputFile}"` |
|
|
454
454
|
| After a step wrote, for each output file | `node _opencrew/core/scripts/caminho.mjs "{name}" conferir --arquivo "{path}"` |
|
|
@@ -476,7 +476,7 @@ root the one-line command of each moment and read the last line (`CAMINHO:OK {pa
|
|
|
476
476
|
and reads `…/v1/pesquisa.md`. Never assume `v1`.
|
|
477
477
|
- **Script that does not run** (no Node, an error, or no `CAMINHO:` line): tell the user once per
|
|
478
478
|
run `Não consegui rodar a conferência de caminhos; sigo pela regra escrita e marco os arquivos como não verificados.`,
|
|
479
|
-
build the path by the rule above (
|
|
479
|
+
build the path by the rule above (your file-writing tool creates the folder) and continue. A file handled
|
|
480
480
|
this way skips its gate and is listed at the final approval:
|
|
481
481
|
`{arquivo} — não verificado: a conferência de caminhos não rodou`.
|
|
482
482
|
|
|
@@ -518,10 +518,10 @@ root the one-line command of each moment and read the last line (`CAMINHO:OK {pa
|
|
|
518
518
|
- Read the step's `model_tier` frontmatter field (if present).
|
|
519
519
|
Valid values: `fast` or `powerful`. If absent or any other value: default to `powerful`.
|
|
520
520
|
- **Before building the subagent prompt**: Resolve all output paths referenced in the step file with the `saida` command (Output Path Transformation, once per group). Store the transformed path(s) in working memory — they will be used both in the prompt and in post-completion verification. Never pass raw paths from the step file to the subagent.
|
|
521
|
-
-
|
|
521
|
+
- Dispatch the step with your IDE's subagent mechanism (an IDE without one: run the step inline, in this conversation):
|
|
522
522
|
- If `model_tier: fast`: use the fastest/lightest model available in your current IDE.
|
|
523
523
|
- If `model_tier: powerful` or absent/invalid: use the default model (no model override needed)
|
|
524
|
-
- In the
|
|
524
|
+
- In the subagent's prompt, include:
|
|
525
525
|
- The full agent persona from the party CSV
|
|
526
526
|
- The full agent `.agent.md` content (persona, principles, voice guidance, anti-patterns)
|
|
527
527
|
- If the agent has tasks: include ALL task files in order with instructions to execute sequentially, piping output from each task to the next
|
|
@@ -558,16 +558,16 @@ root the one-line command of each moment and read the last line (`CAMINHO:OK {pa
|
|
|
558
558
|
(e.g. the organization's name, the main audience), ask: "Isso vale para todas as crews?
|
|
559
559
|
Atualizo o perfil da empresa?" — change `company.md` only after a yes.
|
|
560
560
|
- **If the step frontmatter contains `outputFile`**: after collecting the user's full response,
|
|
561
|
-
insert only the run_id in the `outputFile` path (item 1 of the rule in Output Path Transformation — no version folder, no `saida` command), then write the response to that path
|
|
561
|
+
insert only the run_id in the `outputFile` path (item 1 of the rule in Output Path Transformation — no version folder, no `saida` command), then write the response to that path with your file-writing tool (it creates the folder) before moving to the next step. Checkpoint files are user input captures, not versioned output: they live in the group itself, where `entrada` finds them.
|
|
562
562
|
For the checkpoint that precedes the researcher, use this format:
|
|
563
563
|
```
|
|
564
564
|
# Research Focus
|
|
565
565
|
|
|
566
566
|
**Topic:** {user's typed topic}
|
|
567
567
|
**Time Range:** {selected time range label, e.g., "Últimos 7 dias"}
|
|
568
|
-
**Date:** {
|
|
568
|
+
**Date:** {the date of this run, YYYY-MM-DD}
|
|
569
569
|
```
|
|
570
|
-
For any other checkpoint: `# {the checkpoint's title}`, the user's answer as given (the option chosen and every comment), and `**Date:** {
|
|
570
|
+
For any other checkpoint: `# {the checkpoint's title}`, the user's answer as given (the option chosen and every comment), and `**Date:** {the date of this run, YYYY-MM-DD}`.
|
|
571
571
|
This file is the `inputFile` of the step that follows.
|
|
572
572
|
|
|
573
573
|
### Post-Step Output Validation
|
|
@@ -599,7 +599,7 @@ After a step produces output (subagent or inline) and BEFORE Veto Condition Enfo
|
|
|
599
599
|
- If the step does not declare an `outputFile` (e.g., steps that only produce inline console output) → skip output validation.
|
|
600
600
|
- Checkpoint steps (`type: checkpoint`) are exempt — their output is the user's response, not a file.
|
|
601
601
|
|
|
602
|
-
**IMPORTANT**: Do NOT rely on reading the file
|
|
602
|
+
**IMPORTANT**: Do NOT rely on reading the file yourself to "verify" output: what you read can be misinterpreted. Use ONLY the `conferir` command — its last line is binary and cannot be hallucinated.
|
|
603
603
|
|
|
604
604
|
### Output Contract Validation
|
|
605
605
|
|
|
@@ -643,7 +643,7 @@ After an agent completes a step (before moving to the next step):
|
|
|
643
643
|
- Check each condition (e.g., "slides exceed 30 words", "no CTA", "missing sources")
|
|
644
644
|
3. If ANY veto condition is triggered:
|
|
645
645
|
- Inform user: "⚠️ {Agent Name}'s output triggered a veto: {condition}"
|
|
646
|
-
- Ask the agent to fix the specific issue (re-execute with targeted correction)
|
|
646
|
+
- Ask the agent to fix the specific issue (re-execute with targeted correction), then run the `conferir` command on the rewritten file again (Post-Step Output Validation) before judging the veto once more
|
|
647
647
|
- Maximum 2 veto fix attempts per step
|
|
648
648
|
- After 2 failed attempts, present to user for manual decision
|
|
649
649
|
- **Never auto-fix an irreversible step** (`side_effects: irreversible`): re-executing it
|
|
@@ -778,7 +778,7 @@ One script turns the approved files into `crews/{name}/output/{run_id}/entrega/`
|
|
|
778
778
|
Then proceed to prepend the new row.
|
|
779
779
|
|
|
780
780
|
Read `crews/{name}/_memory/runs.md`. Prepend one new row to the table (immediately after the header row), with:
|
|
781
|
-
- `Data`:
|
|
781
|
+
- `Data`: the date of this run (the first 10 characters of the `run_id`)
|
|
782
782
|
- `Run ID`: the `run_id` for this execution
|
|
783
783
|
- `Tema`: the topic or user request from this run (1 sentence max)
|
|
784
784
|
- `Output`: brief description of what was generated (e.g., "Carrossel 9 slides", "Thread 7 posts")
|
|
@@ -41,7 +41,9 @@ export function erroDeArgumentos({ crew, acao, run, arquivo, secoes }) {
|
|
|
41
41
|
if (!crew) return 'Falta o nome da crew.';
|
|
42
42
|
if (!acao) return `Falta a ação. Ações: ${LISTA}.`;
|
|
43
43
|
if (!ACOES.includes(acao)) return `Ação desconhecida: ${limpar(acao)}. Ações: ${LISTA}.`;
|
|
44
|
-
|
|
44
|
+
// `pasta` sem --run: o script dá o nome à execução (data e hora do computador).
|
|
45
|
+
if (acao !== 'conferir' && acao !== 'pasta' && !run) return MSG.faltaOpcao('--run');
|
|
46
|
+
if (run === '') return MSG.faltaOpcao('--run'); // `--run` sem valor não vira "sem --run"
|
|
45
47
|
if (run !== undefined && !RUN.test(run)) return 'O --run só aceita letras, dígitos, ponto, sublinhado e hífen.';
|
|
46
48
|
if (acao !== 'pasta' && !arquivo) return MSG.faltaOpcao('--arquivo');
|
|
47
49
|
if (secoes !== undefined && !INTEIRO.test(secoes)) return 'O --secoes é um número inteiro a partir de 1.';
|
|
@@ -29,6 +29,22 @@ export function naExecucao(declarado, crew, run) {
|
|
|
29
29
|
return { grupo: [`${saida}${run}`, ...partes].join('/'), nome };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const dois = (n) => String(n).padStart(2, '0');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* O nome de uma execução nova: `AAAA-MM-DD-HHmmss` na hora local; se já existe pasta com esse
|
|
36
|
+
* nome, `-2`, `-3`… (spec fase-u5a-polimento-do-uso.md, decisão 2).
|
|
37
|
+
* @param {Date} agora · @param {string[]} existentes as pastas que já estão em `output/`
|
|
38
|
+
*/
|
|
39
|
+
export function novoRun(agora, existentes) {
|
|
40
|
+
const base = `${agora.getFullYear()}-${dois(agora.getMonth() + 1)}-${dois(agora.getDate())}-${dois(agora.getHours())}${dois(agora.getMinutes())}${dois(agora.getSeconds())}`;
|
|
41
|
+
const usados = new Set(existentes);
|
|
42
|
+
for (let n = 1; ; n++) {
|
|
43
|
+
const nome = n === 1 ? base : `${base}-${n}`;
|
|
44
|
+
if (!usados.has(nome)) return nome;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
/** O número de uma pasta de versão (`v` + número), ou `null` para qualquer outro nome. */
|
|
33
49
|
function numero(nome) {
|
|
34
50
|
const [, digitos] = /^v(\d{1,9})$/.exec(nome) ?? [];
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Caminho da execução de uma crew: diz onde cada passo grava, de onde lê e se o arquivo gravado
|
|
3
3
|
// está lá. Quem calcula é este script, igual em qualquer sistema — a IA não monta o caminho.
|
|
4
4
|
// Uso (na pasta do projeto): node _opencrew/core/scripts/caminho.mjs <crew> <ação> --run <id> [opções]
|
|
5
|
-
// pasta --run <id>
|
|
5
|
+
// pasta [--run <id>] cria crews/<crew>/output/<id>/; sem --run, o id é a
|
|
6
|
+
// data e a hora do computador (AAAA-MM-DD-HHmmss)
|
|
6
7
|
// saida --run <id> --arquivo <declarado> onde o passo grava (abre a pasta de versão seguinte)
|
|
7
8
|
// entrada --run <id> --arquivo <declarado> a saída mais nova desse arquivo
|
|
8
9
|
// conferir --arquivo <caminho já resolvido> [--secoes N] [--tldr]
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
import path from 'node:path';
|
|
18
19
|
import { MSG, dentroDoProjeto, ehPrincipal, realDentroDe } from './comum.mjs';
|
|
19
20
|
import { USO, erroDeArgumentos, lerArgs, limpar } from './caminho/argumentos.mjs';
|
|
20
|
-
import { MOTIVO, daMaisNova, motivoDeReprovacao, naExecucao, normalizar, proximaVersao } from './caminho/nucleo.mjs';
|
|
21
|
+
import { MOTIVO, daMaisNova, motivoDeReprovacao, naExecucao, normalizar, novoRun, proximaVersao } from './caminho/nucleo.mjs';
|
|
21
22
|
import { criarPasta, ehPasta, lerTexto, pastasDe, temConteudo } from './caminho/disco.mjs';
|
|
22
23
|
|
|
23
24
|
const ok = (caminho) => `CAMINHO:OK ${caminho}`;
|
|
@@ -31,7 +32,8 @@ const reprovado = (motivo) => `CAMINHO:REPROVADO ${motivo}`;
|
|
|
31
32
|
*/
|
|
32
33
|
function erroDoArquivo(raiz, crew, { acao, run, arquivo }) {
|
|
33
34
|
if (!dentroDoProjeto(raiz, arquivo)) return MSG.foraDoProjeto(limpar(arquivo));
|
|
34
|
-
|
|
35
|
+
// `pasta` não usa arquivo, e `conferir` recebe o caminho já resolvido: nenhum dos dois o põe na execução.
|
|
36
|
+
const local = acao === 'conferir' || acao === 'pasta' ? null : naExecucao(arquivo, crew, run);
|
|
35
37
|
if (!local) return null;
|
|
36
38
|
if (!local.nome) return `Falta o nome do arquivo em --arquivo: ${limpar(arquivo)}`;
|
|
37
39
|
const execucao = path.resolve(raiz, 'crews', crew, 'output', run);
|
|
@@ -88,23 +90,24 @@ function conferir(raiz, { arquivo, secoes, tldr }) {
|
|
|
88
90
|
return motivo ? reprovado(motivo) : ok(caminho);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
function responder(raiz, crew, args) {
|
|
93
|
+
function responder(raiz, crew, args, agora) {
|
|
92
94
|
if (args.acao === 'saida') return saida(raiz, crew, args);
|
|
93
95
|
if (args.acao === 'entrada') return entrada(raiz, crew, args);
|
|
94
96
|
if (args.acao === 'conferir') return conferir(raiz, args);
|
|
95
|
-
const
|
|
97
|
+
const run = args.run || novoRun(agora(), pastasDe(path.resolve(raiz, 'crews', crew, 'output')));
|
|
98
|
+
const pasta = `crews/${crew}/output/${run}`;
|
|
96
99
|
criarPasta(path.resolve(raiz, pasta));
|
|
97
100
|
return ok(pasta);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
/**
|
|
101
104
|
* @param {string[]} argv
|
|
102
|
-
* @param {object} [deps] `cwd` (a pasta do projeto) e `
|
|
105
|
+
* @param {object} [deps] `cwd` (a pasta do projeto), `escrever` e `agora` (o relógio, para o id da execução)
|
|
103
106
|
* @returns {number} 0 = a linha `CAMINHO:` saiu · 1 = erro de uso, ou falha ao ler ou criar pasta
|
|
104
107
|
* (a linha de uso e o motivo, ou só o erro; sem linha `CAMINHO:`)
|
|
105
108
|
*/
|
|
106
109
|
export function main(argv, deps = {}) {
|
|
107
|
-
const { cwd = process.cwd(), escrever = (s) => process.stdout.write(`${s}\n`) } = deps;
|
|
110
|
+
const { cwd = process.cwd(), escrever = (s) => process.stdout.write(`${s}\n`), agora = () => new Date() } = deps;
|
|
108
111
|
const args = lerArgs(argv);
|
|
109
112
|
const local = localizar(cwd, args);
|
|
110
113
|
if (local.erro) {
|
|
@@ -113,7 +116,7 @@ export function main(argv, deps = {}) {
|
|
|
113
116
|
return 1;
|
|
114
117
|
}
|
|
115
118
|
try {
|
|
116
|
-
escrever(responder(cwd, local.crew, args));
|
|
119
|
+
escrever(responder(cwd, local.crew, args, agora));
|
|
117
120
|
return 0;
|
|
118
121
|
} catch (erro) {
|
|
119
122
|
escrever(`Não consegui resolver o caminho: ${limpar(erro?.message ?? erro)}`);
|
|
@@ -7,7 +7,8 @@ const plural = (n, um, varios) => `${n} ${n === 1 ? um : varios}`;
|
|
|
7
7
|
|
|
8
8
|
export const MSG = {
|
|
9
9
|
nadaACorrigir: 'Nada a corrigir.',
|
|
10
|
-
|
|
10
|
+
corrigido: (arquivo, copia) => `Corrigi: ${arquivo} (cópia: ${copia})`,
|
|
11
|
+
comoCorrigir: 'Para trocar os caminhos que têm sugestão, rode de novo com --corrigir (cada arquivo alterado ganha uma cópia).',
|
|
11
12
|
semCorrecaoAutomatica: (n) => `Não há correção automática para ${n} pendência(s): escolha um candidato ou corrija o caminho na crew.`,
|
|
12
13
|
buscaParcial: (limite) => `Procurei só nos primeiros ${limite} itens do projeto; pode existir um arquivo com esse nome que eu não vi.`,
|
|
13
14
|
linkParaFora: (arquivo) => `Não corrigi \`${arquivo}\`: é um link que aponta para fora da crew. O caminho citado nele continua como estava.`,
|
|
@@ -59,6 +60,7 @@ export function formatar(r) {
|
|
|
59
60
|
}
|
|
60
61
|
if (apontados.length) linhas.push(''); // sem pendência nem alerta, uma linha em branco só
|
|
61
62
|
const alertas = contar('nao-portatil') + contar('nao-conferido');
|
|
62
|
-
|
|
63
|
+
const pendentes = contar('faltando');
|
|
64
|
+
linhas.push(`**Resumo: ${plural(r.refs.length, 'fonte', 'fontes')} — ${contar('ok')} ok, ${plural(pendentes, 'pendente', 'pendentes')}, ${plural(alertas, 'alerta', 'alertas')}**`, '');
|
|
63
65
|
return linhas.join('\n');
|
|
64
66
|
}
|
|
@@ -87,18 +87,17 @@ export async function conferir({ raiz, crew, limite = LIMITE_DA_BUSCA }) {
|
|
|
87
87
|
return { crew, raiz, refs, status, buscaParcial: Boolean(ctx.indice?.parcial), limite };
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** @returns {Promise<string>} o caminho da cópia que ficou */
|
|
90
91
|
async function copiaDeSeguranca(arquivo) {
|
|
91
92
|
const bak = existsSync(`${arquivo}.bak`) ? `${arquivo}.bak-${new Date().toISOString().replace(/[:.]/g, '-')}` : `${arquivo}.bak`;
|
|
92
93
|
await copyFile(arquivo, bak);
|
|
94
|
+
return bak;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
/** Regrava a citação num arquivo; a cópia .bak é feita uma vez por arquivo. */
|
|
97
|
+
/** Regrava a citação num arquivo; a cópia .bak é feita uma vez por arquivo (`tocados`: arquivo → cópia). */
|
|
96
98
|
async function regravar(arquivo, item, tocados) {
|
|
97
99
|
const texto = await readFile(arquivo, 'utf8');
|
|
98
|
-
if (!tocados.has(arquivo))
|
|
99
|
-
await copiaDeSeguranca(arquivo);
|
|
100
|
-
tocados.add(arquivo);
|
|
101
|
-
}
|
|
100
|
+
if (!tocados.has(arquivo)) tocados.set(arquivo, await copiaDeSeguranca(arquivo));
|
|
102
101
|
await writeFile(arquivo, trocarCitacao(texto, item, path.basename(arquivo) === 'crew.yaml'));
|
|
103
102
|
}
|
|
104
103
|
|
|
@@ -118,9 +117,10 @@ function guardaDeEscrita(raiz, crew) {
|
|
|
118
117
|
* Troca, nos arquivos da crew, cada caminho com sugestão única — só a citação que a coleta leu,
|
|
119
118
|
* nunca um pedaço de outro texto. O que a guarda de escrita barra não muda: `avisar` recebe uma
|
|
120
119
|
* linha por arquivo pulado, ou uma só quando a crew inteira é um link para fora do projeto.
|
|
120
|
+
* `gravado` recebe cada arquivo alterado e a cópia que ficou dele.
|
|
121
121
|
* @returns quantos caminhos foram gravados, em ao menos um arquivo
|
|
122
122
|
*/
|
|
123
|
-
export async function corrigir({ resultado, avisar = () => {} }) {
|
|
123
|
+
export async function corrigir({ resultado, avisar = () => {}, gravado = () => {} }) {
|
|
124
124
|
const { raiz, crew } = resultado;
|
|
125
125
|
const comSugestao = resultado.refs.filter((i) => i.sugestao && i.estado !== 'ok');
|
|
126
126
|
const podeGravar = comSugestao.length ? guardaDeEscrita(raiz, crew) : null;
|
|
@@ -128,7 +128,7 @@ export async function corrigir({ resultado, avisar = () => {} }) {
|
|
|
128
128
|
if (comSugestao.length) avisar(MSG.crewLigadaParaFora(crew));
|
|
129
129
|
return 0;
|
|
130
130
|
}
|
|
131
|
-
const tocados = new
|
|
131
|
+
const tocados = new Map();
|
|
132
132
|
const pulados = new Set();
|
|
133
133
|
let gravados = 0;
|
|
134
134
|
for (const item of comSugestao) {
|
|
@@ -138,19 +138,22 @@ export async function corrigir({ resultado, avisar = () => {} }) {
|
|
|
138
138
|
if (dentro.length) gravados += 1;
|
|
139
139
|
}
|
|
140
140
|
for (const arquivo of pulados) avisar(MSG.linkParaFora(relativoAoProjeto(raiz, arquivo)));
|
|
141
|
+
for (const [arquivo, copia] of tocados) gravado(relativoAoProjeto(raiz, arquivo), path.basename(copia));
|
|
141
142
|
return gravados;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* --corrigir: troca o que tem sugestão única e diz cada arquivo alterado, com a cópia; o relatório
|
|
147
|
+
* sai uma vez só — o de depois, quando algo mudou. Diz também o que pulou e o que ficou sem correção.
|
|
148
|
+
*/
|
|
145
149
|
async function corrigirEAvisar(r, escrever) {
|
|
146
150
|
const pulados = [];
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
151
|
+
const feitos = [];
|
|
152
|
+
const n = await corrigir({ resultado: r, avisar: (linha) => pulados.push(linha), gravado: (arquivo, copia) => feitos.push(MSG.corrigido(arquivo, copia)) });
|
|
153
|
+
const atual = n ? await conferir({ raiz: r.raiz, crew: r.crew }) : r;
|
|
154
|
+
for (const linha of feitos) escrever(linha);
|
|
155
|
+
if (n) escrever('');
|
|
156
|
+
escrever(formatar(atual));
|
|
154
157
|
for (const linha of pulados) escrever(linha);
|
|
155
158
|
const semSugestao = atual.refs.filter((i) => i.estado === 'faltando' && !i.sugestao).length;
|
|
156
159
|
if (semSugestao) escrever(MSG.semCorrecaoAutomatica(semSugestao));
|
|
@@ -177,8 +180,11 @@ export async function main(argv, { cwd = process.cwd(), escrever = (s) => proces
|
|
|
177
180
|
}
|
|
178
181
|
try {
|
|
179
182
|
let r = await conferir({ raiz: cwd, crew });
|
|
180
|
-
escrever(formatar(r));
|
|
181
183
|
if (argv.includes('--corrigir')) r = await corrigirEAvisar(r, escrever);
|
|
184
|
+
else {
|
|
185
|
+
escrever(formatar(r));
|
|
186
|
+
if (r.refs.some((i) => i.sugestao && i.estado !== 'ok')) escrever(MSG.comoCorrigir);
|
|
187
|
+
}
|
|
182
188
|
escrever(`FONTES:${r.status}`);
|
|
183
189
|
return 0;
|
|
184
190
|
} catch (erroDeLeitura) {
|
|
@@ -52,7 +52,7 @@ function formato(crew) {
|
|
|
52
52
|
const n = passos.length;
|
|
53
53
|
return [
|
|
54
54
|
`${n} ${plural(n, 'passo que a revisão confere não diz', 'passos que a revisão confere não dizem')} o formato do texto.`,
|
|
55
|
-
|
|
55
|
+
'Sem o formato, o redator não recebe o guia desse tipo de texto, e o verificador procura no arquivo peças de rede (legenda, post, título de blog).',
|
|
56
56
|
`Passos: ${passos.map((p) => `${p.numero} (${saidaDe(p)})`).join(', ')}`,
|
|
57
57
|
'Para consertar: --aplicar "formato:<passo>=<formato>"',
|
|
58
58
|
];
|
|
@@ -37,6 +37,7 @@ function lerTabela(linhas, i, estado) {
|
|
|
37
37
|
while (fim < linhas.length && linhas[fim].trim() && linhas[fim].includes('|')) fim++;
|
|
38
38
|
const brutas = [linhas[i], ...linhas.slice(i + 2, fim)].map(celulas);
|
|
39
39
|
const colunas = Math.max(...brutas.map((l) => l.length));
|
|
40
|
+
estado.avisos.celulas += brutas.slice(1).filter((l) => l.length > brutas[0].length).length;
|
|
40
41
|
const completas = brutas.map((l) => [...l, ...Array(colunas - l.length).fill('')]);
|
|
41
42
|
estado.por({ tipo: 'tabela', linhas: completas.map((l) => l.map((c) => lerLinha(c, estado.avisos))) });
|
|
42
43
|
return fim;
|
|
@@ -56,7 +57,7 @@ function lerBloco(linha, avisos) {
|
|
|
56
57
|
|
|
57
58
|
/** Onde os blocos se juntam: a quebra de página pedida vale para o próximo bloco, uma vez só. */
|
|
58
59
|
function novoEstado() {
|
|
59
|
-
const estado = { blocos: [], avisos: { imagens: 0, desconhecidas: 0, semFim: 0, invalidos: 0 }, quebra: false };
|
|
60
|
+
const estado = { blocos: [], avisos: { imagens: 0, desconhecidas: 0, semFim: 0, invalidos: 0, celulas: 0 }, quebra: false };
|
|
60
61
|
estado.por = (bloco) => {
|
|
61
62
|
estado.blocos.push(estado.quebra ? { ...bloco, quebra: true } : bloco);
|
|
62
63
|
estado.quebra = false;
|
|
@@ -22,6 +22,7 @@ function avisosDe(c) {
|
|
|
22
22
|
c.imagens && plural(c.imagens, '1 imagem não incluída: o Word não leva imagem no texto.', '{n} imagens não incluídas: o Word não leva imagem no texto.'),
|
|
23
23
|
c.desconhecidas && plural(c.desconhecidas, '1 linha com marcação desconhecida (`:::`) ficou como texto.', '{n} linhas com marcação desconhecida (`:::`) ficaram como texto.'),
|
|
24
24
|
c.semFim && plural(c.semFim, 'Bloco de assinaturas sem a linha `:::` no fim: ficou como texto.', '{n} blocos de assinaturas sem a linha `:::` no fim: ficaram como texto.'),
|
|
25
|
+
c.celulas && plural(c.celulas, '1 linha de tabela com mais células que o cabeçalho: a coluna a mais ficou sem título.', '{n} linhas de tabela com mais células que o cabeçalho: a coluna a mais ficou sem título.'),
|
|
25
26
|
c.invalidos && plural(c.invalidos, '1 caractere inválido removido.', '{n} caracteres inválidos removidos.'),
|
|
26
27
|
].filter(Boolean);
|
|
27
28
|
}
|
|
@@ -10,6 +10,7 @@ export const PADRAO = Object.freeze({
|
|
|
10
10
|
|
|
11
11
|
export const MSG = {
|
|
12
12
|
chave: (n, chave) => `Perfil, linha ${n}: não conheço a chave ${chave}.`,
|
|
13
|
+
grafia: (n, certa) => `Perfil, linha ${n}: a chave se escreve ${certa}: em minúsculas, sem acento, no começo da linha e sem espaço antes dos dois-pontos.`,
|
|
13
14
|
valor: (n, chave, esperado, valor) => `Perfil, linha ${n}: ${chave} precisa ser ${esperado}. Recebi: ${valor}.`,
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -38,6 +39,17 @@ const CHAVES = {
|
|
|
38
39
|
tamanho_corpo_pt: { esperado: 'um número de 8 a 14 (aceita meio ponto)', ler: (valor) => (numero(valor) >= 8 && numero(valor) <= 14 && Number.isInteger(numero(valor) * 2) ? numero(valor) : undefined) },
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
// Linha que parece "chave: valor" mas não foi lida como chave: maiúscula, acento ou espaço antes.
|
|
43
|
+
const QUASE = /^[ \t]*([\p{L}0-9_]+)[ \t]*:(?!\/\/)/u;
|
|
44
|
+
const semAcento = (s) => s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
|
|
45
|
+
|
|
46
|
+
/** A chave conhecida que a linha quis escrever, quando não a escreveu do jeito certo; senão, null. */
|
|
47
|
+
function chaveQuaseCerta(linha) {
|
|
48
|
+
const [, escrita] = QUASE.exec(linha) ?? [];
|
|
49
|
+
const certa = escrita ? semAcento(escrita) : '';
|
|
50
|
+
return Object.hasOwn(CHAVES, certa) && !linha.startsWith(`${certa}:`) ? certa : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
41
53
|
/** Tira as aspas em volta do valor, se as duas pontas têm a mesma. */
|
|
42
54
|
function semAspas(valor) {
|
|
43
55
|
const v = valor.trim();
|
|
@@ -58,6 +70,8 @@ export function lerPerfil(bruto) {
|
|
|
58
70
|
const semBom = bruto.charCodeAt(0) === 0xfeff ? bruto.slice(1) : bruto;
|
|
59
71
|
for (const [i, linha] of semBom.split(/\r\n?|\n/).entries()) {
|
|
60
72
|
const [, chave, escrito = ''] = LINHA.exec(linha.trimEnd()) ?? [];
|
|
73
|
+
const quase = chave ? null : chaveQuaseCerta(linha);
|
|
74
|
+
if (quase) return { perfil, linhas, erro: MSG.grafia(i + 1, quase) };
|
|
61
75
|
if (!chave) continue;
|
|
62
76
|
if (!Object.hasOwn(CHAVES, chave)) return { perfil, linhas, erro: MSG.chave(i + 1, chave) };
|
|
63
77
|
linhas[chave] = i + 1;
|
|
@@ -14,6 +14,8 @@ export const MSG = {
|
|
|
14
14
|
};
|
|
15
15
|
/** A lista fixa do que o script nunca confere. */
|
|
16
16
|
export const NUNCA_CONFERIDO = ['Links e fatos citados no texto.', 'Texto dentro das imagens.', 'Aparência final em cada rede.'];
|
|
17
|
+
/** Canal que não é de rede: nele não há imagem a publicar nem aparência de rede a conferir. */
|
|
18
|
+
const SEM_REDE = new Set(['documentos']);
|
|
17
19
|
|
|
18
20
|
const ehNaoMedido = (i) => i.item === NAO_MEDIDO || i.item === NAO_VERIFICADO;
|
|
19
21
|
const origem = (arquivo) => `origem: \`${arquivo}\``;
|
|
@@ -63,7 +65,8 @@ export function naoConferido(raiz, itens, r) {
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
if (r.naoTexto.length) linhas.push(`${NAO_VERIFICADO} — não é texto: ${r.naoTexto.join(', ')}`);
|
|
66
|
-
|
|
68
|
+
const temRede = itens.some((i) => i.canal && !SEM_REDE.has(i.canal));
|
|
69
|
+
return [...linhas, ...(temRede ? NUNCA_CONFERIDO : NUNCA_CONFERIDO.slice(0, 1))];
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
const LIMITE = { legenda: 'caption_max_chars', post: 'post_max_chars', tweet: 'tweet_max_chars' };
|
|
@@ -80,7 +80,8 @@ function separarPecas(item, texto, itens, p) {
|
|
|
80
80
|
async function separarItem(item, itens, p) {
|
|
81
81
|
if (ehEditavel(item, itens)) return p.copias.push(copia(item, EDITAVEIS, 'editavel'));
|
|
82
82
|
if (!item.canal) {
|
|
83
|
-
|
|
83
|
+
// `texto-livre` é o formato de quem não tem canal: estar em `outros/` é o esperado, não um aviso.
|
|
84
|
+
if (item.formato !== 'texto-livre') p.avisos.push({ pasta: OUTROS, tela: MSG.semCanal(item.rel) });
|
|
84
85
|
return p.copias.push(copia(item, OUTROS, 'copia'));
|
|
85
86
|
}
|
|
86
87
|
// Documento oficial: vai inteiro, sem procura de peças; `documentos.mjs` converte o texto em Word.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// O que o Word não vai converter, visto antes do revisor: no texto de formato `documento-oficial`
|
|
2
|
+
// o verificador lê as mesmas marcações que o conversor e põe um alerta para cada tipo de sobra.
|
|
3
|
+
// Alerta, nunca bloqueio: quem decide é o revisor. Spec: fase-u5a-polimento-do-uso.md, regra 6.
|
|
4
|
+
import { lerMarkdown } from '../documento/markdown.mjs';
|
|
5
|
+
import { item } from './regras.mjs';
|
|
6
|
+
|
|
7
|
+
export const FORMATO_DE_DOCUMENTO = 'documento-oficial';
|
|
8
|
+
const NOME = 'Documento Word';
|
|
9
|
+
const plural = (n, um, varios) => (n === 1 ? um : varios.replace('{n}', n));
|
|
10
|
+
const FRASES = [
|
|
11
|
+
['imagens', '1 imagem não entra no documento', '{n} imagens não entram no documento'],
|
|
12
|
+
['desconhecidas', '1 marcação `:::` desconhecida vai como texto', '{n} marcações `:::` desconhecidas vão como texto'],
|
|
13
|
+
['semFim', '1 bloco de assinaturas sem a linha `:::` do fim vai como texto', '{n} blocos de assinaturas sem a linha `:::` do fim vão como texto'],
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/** Os alertas de um texto que vai virar Word; lista vazia quando tudo converte. */
|
|
17
|
+
export function alertasDeDocumento(texto) {
|
|
18
|
+
const { avisos } = lerMarkdown(texto);
|
|
19
|
+
return FRASES.filter(([chave]) => avisos[chave]).map(([chave, um, varios]) => item(NOME, null, null, 'alerta', plural(avisos[chave], um, varios)));
|
|
20
|
+
}
|
|
@@ -108,7 +108,8 @@ function naoMedidos({ pecas, corpo, formato, limites }, semLimite, medidas) {
|
|
|
108
108
|
for (const cabecalho of BLOG.includes(formato) ? secoesDeOutroCanal(corpo) : []) {
|
|
109
109
|
linhas.push(item(NAO_MEDIDO, null, null, null, `seção de outro canal num arquivo de blog: ${curto(cabecalho)}`));
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
// Formato que não declara limite nenhum (documento-oficial, texto-livre) não tem o que medir.
|
|
112
|
+
if (formato && !tipos && Object.keys(limites[formato] ?? {}).length && !medidas) {
|
|
112
113
|
linhas.push(item(NAO_MEDIDO, null, null, null, `o verificador ainda não mede os limites do formato ${formato}`));
|
|
113
114
|
}
|
|
114
115
|
return linhas;
|
|
@@ -25,6 +25,7 @@ import { lerLimites, lerDominioDoSite, semFrontmatter } from './verificar/leitur
|
|
|
25
25
|
import { lerProibicoes } from './verificar/proibicoes.mjs';
|
|
26
26
|
import { lerPecas } from './verificar/pecas.mjs';
|
|
27
27
|
import { medirPecas } from './verificar/medicao.mjs';
|
|
28
|
+
import { FORMATO_DE_DOCUMENTO, alertasDeDocumento } from './verificar/documento.mjs';
|
|
28
29
|
import { regrasGerais, temVariavel, item, FALTA_INFO, NAO_MEDIDO, NAO_VERIFICADO } from './verificar/regras.mjs';
|
|
29
30
|
import { formatarRelatorio } from './verificar/relatorio.mjs';
|
|
30
31
|
import { MSG as GRAVACAO, caminhoDoRelatorio, gravarRelatorio } from './verificar/gravacao.mjs';
|
|
@@ -86,7 +87,8 @@ async function verificarArquivo({ arquivo, formato }, ctx, regraDeTeste) {
|
|
|
86
87
|
const envio = /^(email|whatsapp)-/.test(formato ?? '');
|
|
87
88
|
if (envio && temVariavel(lido.texto)) ctx.notas.add(NOTA_VARIAVEL);
|
|
88
89
|
const gerais = regrasGerais(lido.texto, ctx.proibidos, { variavelBloqueia: !envio });
|
|
89
|
-
const
|
|
90
|
+
const doWord = formato === FORMATO_DE_DOCUMENTO && lido.comPecas ? alertasDeDocumento(lido.texto) : [];
|
|
91
|
+
const extras = [...doWord, ...(regraDeTeste ? (await regraDeTeste({ arquivo, formato, texto: lido.texto })) ?? [] : [])];
|
|
90
92
|
const naoMedido = (i) => i.item === NAO_MEDIDO;
|
|
91
93
|
const itens = [...medidos.filter((i) => !naoMedido(i)), ...gerais, ...extras, ...medidos.filter(naoMedido)];
|
|
92
94
|
return { itens, fecho: fechoDe(itens, medidas, formato) };
|