@aksp/opencrew 1.6.2 → 1.7.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 +102 -0
- package/README.md +70 -13
- package/package.json +2 -2
- package/src/cli.js +15 -38
- package/src/commands/init.js +41 -44
- package/src/commands/update.js +78 -75
- package/src/lib/blocos.js +148 -0
- package/src/lib/deteccao.js +69 -0
- package/src/lib/fsx.js +1 -55
- package/src/lib/ides.js +4 -0
- package/src/lib/legado.js +142 -0
- package/src/lib/manifest.js +67 -26
- package/src/lib/mcp.js +131 -0
- package/src/lib/migrations.js +77 -74
- package/src/lib/node-version.js +43 -0
- package/src/lib/prompts.js +25 -2
- package/src/lib/resumo.js +125 -0
- package/templates/.mcp.json +1 -1
- package/templates/AGENTS.md +20 -6
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/core/best-practices/social-networks-publishing.md +14 -14
- package/templates/_opencrew/core/escritorio/animacao.js +64 -0
- package/templates/_opencrew/core/escritorio/app.js +137 -0
- package/templates/_opencrew/core/escritorio/cena.js +132 -0
- package/templates/_opencrew/core/escritorio/demo.js +79 -0
- package/templates/_opencrew/core/escritorio/escala.js +27 -0
- package/templates/_opencrew/core/escritorio/index.html +166 -0
- package/templates/_opencrew/core/escritorio/modelo-agentes.js +93 -0
- package/templates/_opencrew/core/escritorio/modelo-estado.js +71 -0
- package/templates/_opencrew/core/escritorio/modelo-mesas.js +81 -0
- package/templates/_opencrew/core/escritorio/modelo-pagina.js +95 -0
- package/templates/_opencrew/core/escritorio/modelo-textos.js +65 -0
- package/templates/_opencrew/core/escritorio/modelo-visao.js +91 -0
- package/templates/_opencrew/core/escritorio/modelo.js +29 -0
- package/templates/_opencrew/core/escritorio/painel.js +120 -0
- package/templates/_opencrew/core/escritorio/quadro.js +106 -0
- package/templates/_opencrew/core/escritorio/rota.js +62 -0
- package/templates/_opencrew/core/escritorio/rotulos.js +78 -0
- package/templates/_opencrew/core/escritorio/sprites-mesa.js +122 -0
- package/templates/_opencrew/core/escritorio/sprites-sala.js +92 -0
- package/templates/_opencrew/core/escritorio/sprites.js +187 -0
- package/templates/_opencrew/core/prompts/build.prompt.md +3 -3
- package/templates/_opencrew/core/prompts/export.prompt.md +1 -1
- package/templates/_opencrew/core/prompts/repair.prompt.md +7 -12
- package/templates/_opencrew/core/prompts/sherlock-shared.md +5 -5
- package/templates/_opencrew/core/runner.pipeline.md +76 -139
- package/templates/_opencrew/core/scripts/comum.mjs +49 -4
- package/templates/_opencrew/core/scripts/conferir-fontes/busca.mjs +42 -3
- package/templates/_opencrew/core/scripts/conferir-fontes/relatorio.mjs +18 -6
- package/templates/_opencrew/core/scripts/conferir-fontes.mjs +97 -39
- package/templates/_opencrew/core/scripts/escritorio/leitura.mjs +31 -0
- package/templates/_opencrew/core/scripts/escritorio/porta.mjs +98 -0
- package/templates/_opencrew/core/scripts/escritorio/projeto.mjs +29 -0
- package/templates/_opencrew/core/scripts/escritorio/servidor.mjs +78 -0
- package/templates/_opencrew/core/scripts/escritorio.mjs +117 -0
- package/templates/_opencrew/core/scripts/estado/argumentos.mjs +61 -0
- package/templates/_opencrew/core/scripts/estado/arquivo.mjs +53 -0
- package/templates/_opencrew/core/scripts/estado/decisao.mjs +56 -0
- package/templates/_opencrew/core/scripts/estado/elenco.mjs +58 -0
- package/templates/_opencrew/core/scripts/estado/nucleo.mjs +113 -0
- package/templates/_opencrew/core/scripts/estado/preferencia.mjs +24 -0
- package/templates/_opencrew/core/scripts/estado.mjs +96 -0
- package/templates/_opencrew/core/scripts/verificar.mjs +7 -4
- package/templates/_opencrew/core/skills.engine.md +7 -3
- package/templates/gitignore +1 -0
- package/templates/skills/blotato/SKILL.md +39 -10
- package/templates/skills/image-ai-generator/SKILL.md +18 -5
- package/templates/skills/image-ai-generator/scripts/generate.py +52 -10
- package/templates/skills/instagram-publisher/SKILL.md +4 -0
- package/templates/skills/opencrew-skill-creator/references/skill-format.md +1 -0
- package/templates/skills/resend/SKILL.md +52 -13
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
|
|
6
6
|
You are the Pipeline Runner. Your job is to execute a crew's pipeline step by step.
|
|
7
7
|
|
|
8
|
+
## Safe names in commands (nome seguro)
|
|
9
|
+
|
|
10
|
+
Applies to EVERY command below and in any prompt or skill. A path of the crew or of the user's
|
|
11
|
+
project goes into a command only between double quotes and only if it is made of letters (accents
|
|
12
|
+
included), digits, space and `. _ - / \ : ( )`. With any other character (`$`, backtick, quote,
|
|
13
|
+
`%`, `!`, `,`, `;`, `&`, `|`, `<`, `>`, line break) do NOT build the command:
|
|
14
|
+
- **Crew folder** → stop: "⚠️ A pasta da crew (`crews/{name}`) tem um caractere que não posso usar
|
|
15
|
+
em comandos ({caractere}). Renomeie a pasta e rode de novo."
|
|
16
|
+
- **Output file** (`inputFile`, `outputFile`, any file passed to a script) → ask and wait:
|
|
17
|
+
```
|
|
18
|
+
⚠️ O nome `{caminho}` tem um caractere que não posso usar em comandos ({caractere}). Use só letras, números, espaço, ponto, hífen, sublinhado e parênteses.
|
|
19
|
+
1. Parar para você renomear (ajuste também o `outputFile` do passo)
|
|
20
|
+
2. Seguir sem conferir este arquivo
|
|
21
|
+
```
|
|
22
|
+
On 2, run no command with that file (no validation gate, not sent to the checker) and list it at
|
|
23
|
+
the final approval: `{arquivo} — não verificado: nome com caractere que não vai em comando`.
|
|
24
|
+
|
|
8
25
|
## Initialization
|
|
9
26
|
|
|
10
27
|
Before starting execution:
|
|
@@ -16,18 +33,7 @@ Before starting execution:
|
|
|
16
33
|
- Crew memory from `crews/{name}/_memory/memories.md`
|
|
17
34
|
- User preferences from `_opencrew/_memory/preferences.md`
|
|
18
35
|
|
|
19
|
-
1a. **
|
|
20
|
-
optional, opt-in feature that most installs never use (it requires running the
|
|
21
|
-
separate dashboard app from source — see README). Scan the already-loaded
|
|
22
|
-
`preferences.md` for a `Dashboard:` field:
|
|
23
|
-
- If its value is `enabled` (as written by onboarding: `- **Dashboard:** enabled`, or the
|
|
24
|
-
plain form `Dashboard: enabled`) → set `dashboard_enabled = true` for this run.
|
|
25
|
-
- Otherwise (`disabled`, missing, or preferences.md not configured yet) →
|
|
26
|
-
set `dashboard_enabled = false`. This is the default.
|
|
27
|
-
Store `dashboard_enabled` in working memory for the rest of this run. Every
|
|
28
|
-
`state.json` read/write instruction in this document is conditional on it —
|
|
29
|
-
when `false`, skip ALL of them; never create, update, or delete
|
|
30
|
-
`crews/{name}/state.json`.
|
|
36
|
+
1a. **Escritório toggle** — the optional live view is off unless `preferences.md` turns it on (see "Escritório" below).
|
|
31
37
|
|
|
32
38
|
> **Note on language**: The structural labels listed below are **fixed PT-BR** and must
|
|
33
39
|
> never be translated — opencrew's primary supported audience is PT-BR (see AGENTS.md →
|
|
@@ -49,7 +55,7 @@ Before starting execution:
|
|
|
49
55
|
|
|
50
56
|
1b. **Memory format migration** — After loading `memories.md`, check whether it uses the new format by scanning for the `## Estilo de Escrita` section header:
|
|
51
57
|
```bash
|
|
52
|
-
[ -f crews/{name}/_memory/memories.md ] && grep -q "## Estilo de Escrita" crews/{name}/_memory/memories.md && echo "NEW_FORMAT" || echo "OLD_FORMAT"
|
|
58
|
+
[ -f "crews/{name}/_memory/memories.md" ] && grep -q "## Estilo de Escrita" "crews/{name}/_memory/memories.md" && echo "NEW_FORMAT" || echo "OLD_FORMAT"
|
|
53
59
|
```
|
|
54
60
|
- If `NEW_FORMAT` → proceed normally.
|
|
55
61
|
- If `OLD_FORMAT` (or file is empty / does not exist) → migrate before proceeding:
|
|
@@ -74,7 +80,7 @@ Before starting execution:
|
|
|
74
80
|
(Use the crew's display name for `{crew-name}`, and the crew code for `{name}` in file paths — they refer to the same crew.)
|
|
75
81
|
b. Check if `crews/{name}/_memory/runs.md` exists:
|
|
76
82
|
```bash
|
|
77
|
-
test -f crews/{name}/_memory/runs.md && echo "EXISTS" || echo "MISSING"
|
|
83
|
+
test -f "crews/{name}/_memory/runs.md" && echo "EXISTS" || echo "MISSING"
|
|
78
84
|
```
|
|
79
85
|
If `MISSING`, create it with:
|
|
80
86
|
```markdown
|
|
@@ -102,9 +108,10 @@ Before starting execution:
|
|
|
102
108
|
On 1, run the same command with `--corrigir`, show the new result and re-read `crew.yaml` and
|
|
103
109
|
any agent file already loaded (it may have changed them); 1d then loads the sources from the
|
|
104
110
|
corrected paths. If the new result still ends in `FONTES:PENDENTE`, ask again with options 2 and
|
|
105
|
-
3 only.
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
3 only. Alerts — not portable (absolute paths) or "não conferido" (a network path or a site
|
|
112
|
+
address: the script never accesses the network) — are mentioned once, without stopping. If the
|
|
113
|
+
script did not run (no Node, an error, or no `FONTES:` status line), tell the user "⚠️ A
|
|
114
|
+
conferência de fontes não rodou: {motivo}" and continue; the final approval repeats the warning.
|
|
108
115
|
|
|
109
116
|
1d. **Project sources (`fontes:`)** — if `crew.yaml` has a `fontes:` list (files or folders of
|
|
110
117
|
the user's project, paths relative to the project root), read them now: a file in full up to
|
|
@@ -224,43 +231,45 @@ Before starting execution:
|
|
|
224
231
|
- Format: `YYYY-MM-DD-HHmmss` using the current timestamp (e.g. `2026-03-03-143022`)
|
|
225
232
|
- Check if `crews/{name}/output/{run_id}/` already exists
|
|
226
233
|
- If it does (sub-second collision), append `-2`, `-3`, etc. until the folder does not exist
|
|
227
|
-
- Create the folder using Bash: `mkdir -p crews/{name}/output/{run_id}`
|
|
234
|
+
- Create the folder using Bash: `mkdir -p "crews/{name}/output/{run_id}"`
|
|
228
235
|
- Store `run_id` in working memory for this run — it will be used for ALL output paths
|
|
229
|
-
6. **
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
236
|
+
6. **Escritório** — if it is on, run `iniciar`, then one `pular` per deselected agent, one after the other (see "Escritório" below).
|
|
237
|
+
|
|
238
|
+
## Escritório (optional live view)
|
|
239
|
+
|
|
240
|
+
A local page that shows the crew at work, off by default. Follow this section only when the
|
|
241
|
+
already-loaded `preferences.md` has `Dashboard: enabled` (written `- **Dashboard:** enabled` or
|
|
242
|
+
plain `Dashboard: enabled`, any letter case); otherwise run none of these commands. When it is on,
|
|
243
|
+
run via Bash, from the project root, the one-line command of each moment:
|
|
244
|
+
|
|
245
|
+
| Moment | Command |
|
|
246
|
+
|---|---|
|
|
247
|
+
| Start of the run (Initialization, step 6) | `node _opencrew/core/scripts/estado.mjs "{name}" iniciar --passos {N}` |
|
|
248
|
+
| Right after `iniciar`, once per deselected agent | `node _opencrew/core/scripts/estado.mjs "{name}" pular --agente {id}` |
|
|
249
|
+
| Before each step, each time it starts | `node _opencrew/core/scripts/estado.mjs "{name}" passo --n {K} --agente {id} --rotulo "{rótulo}" --mensagem "{frase}"` |
|
|
250
|
+
| Before asking the question of a checkpoint (instead of `passo`) | `node _opencrew/core/scripts/estado.mjs "{name}" checkpoint --n {K} --agente {id} --rotulo "{rótulo}"` |
|
|
251
|
+
| End of the run (After Pipeline Completion) | `node _opencrew/core/scripts/estado.mjs "{name}" concluir` |
|
|
252
|
+
| Run aborted after `iniciar`, by the user or by an error | `node _opencrew/core/scripts/estado.mjs "{name}" falhar --motivo "{motivo}"` |
|
|
253
|
+
|
|
254
|
+
- **One at a time** — Run these commands one at a time, waiting for the `ESTADO:` line of each
|
|
255
|
+
before the next — never in parallel or in the background (each one reads and rewrites the same file).
|
|
256
|
+
- **Values** — `{name}`: the crew code. `{N}`: how many steps will run, checkpoints included (a
|
|
257
|
+
deselected agent's steps do not count). `{K}`: the step's position among them, from 1. `{id}`: the agent's `id` column in
|
|
258
|
+
`crew-party.csv`; a step or checkpoint with no `agent:` goes without `--agente`
|
|
259
|
+
(the table shows the full form). `{rótulo}`: the step's name, in
|
|
260
|
+
a few words. `--mensagem` goes only when the agent changed since the last `passo` (so never on the first
|
|
261
|
+
one): one sentence on what the previous agent delivered — never look at the next step. `{motivo}`: why the run stopped.
|
|
262
|
+
- **Text on the command line** — `--rotulo`, `--mensagem` and `--motivo` go between double quotes,
|
|
263
|
+
on one line, starting with a letter or a digit, with only letters (accents included), digits,
|
|
264
|
+
spaces and `. , : ; - ( ) / ?`. Drop every other sign (quotes of any kind, `$`, backtick, `\`,
|
|
265
|
+
`%`, `!`, emoji). If no text is left, omit the option. Write them in the user's language.
|
|
266
|
+
- **After `iniciar`**, when it answers `ESTADO:OK`, show the user once:
|
|
267
|
+
`Escritório ligado. Se a página não estiver aberta, rode em outro terminal: node _opencrew/core/scripts/escritorio.mjs`
|
|
268
|
+
- **The Escritório never stops the run.** A command that fails, does not run or answers
|
|
269
|
+
`ESTADO:IGNORADO`: go on, do not repeat that event, ask nothing, and tell the user once per run,
|
|
270
|
+
in one line: `O escritório não foi atualizado nesta execução; o trabalho segue normalmente.` With
|
|
271
|
+
the reason "escritório desligado", say nothing and stop calling the script for the rest of this run.
|
|
272
|
+
- The script is the only writer: never read, write or describe `crews/{name}/state.json` yourself.
|
|
264
273
|
|
|
265
274
|
## Execution Rules
|
|
266
275
|
|
|
@@ -307,13 +316,14 @@ Before executing any step that references an agent:
|
|
|
307
316
|
```
|
|
308
317
|
If the step has no `format:` field, skip this step entirely (backward compatible).
|
|
309
318
|
6. **Inject skill context (Two-Tier)**:
|
|
310
|
-
a. Build a Tier 1 skill index from each declared skill's frontmatter `name` and `
|
|
311
|
-
b. Append the index after format injection:
|
|
319
|
+
a. Build a Tier 1 skill index from each declared skill's frontmatter `name`, `description` and `side_effects` (~30 tokens per skill)
|
|
320
|
+
b. Append the index after format injection (the second form is for every skill with `side_effects: irreversible`):
|
|
312
321
|
```
|
|
313
322
|
--- AVAILABLE SKILLS ---
|
|
314
323
|
- {skill-id}: {description} (type: {type})
|
|
324
|
+
- {skill-id}: {description} (type: {type}) — irreversível: carregue as instruções desta skill e peça a confirmação antes de usar
|
|
315
325
|
```
|
|
316
|
-
c. If the step's frontmatter contains `skills_needed: [...]`, load Tier 2 (full SKILL.md body) for those skills immediately
|
|
326
|
+
c. If the step's frontmatter contains `skills_needed: [...]`, load Tier 2 (full SKILL.md body) for those skills immediately; for a skill with `side_effects: irreversible`, always load Tier 2 before its first use
|
|
317
327
|
d. Otherwise, Tier 2 is loaded on-demand when the agent invokes a skill during execution
|
|
318
328
|
e. See `_opencrew/core/skills.engine.md` Operation 6 for full details
|
|
319
329
|
|
|
@@ -464,7 +474,7 @@ Apply to every path that was transformed in Step 1:
|
|
|
464
474
|
|
|
465
475
|
2. Detect existing versions for this group using Bash:
|
|
466
476
|
```bash
|
|
467
|
-
ls -1 crews/{name}/output/{run_id}/{relative-group}/ 2>/dev/null | grep -E '^v[0-9]+$' | sort -V | tail -1
|
|
477
|
+
ls -1 "crews/{name}/output/{run_id}/{relative-group}/" 2>/dev/null | grep -E '^v[0-9]+$' | sort -V | tail -1
|
|
468
478
|
```
|
|
469
479
|
- If the command returns a version (e.g. `v2`) → use `v3`
|
|
470
480
|
(Always increment the highest version found, even if lower versions have gaps — e.g. if `v1` and `v3` exist, use `v4`)
|
|
@@ -485,38 +495,15 @@ Apply this transformation consistently for every write in this step.
|
|
|
485
495
|
0. **Agent deselection check** — Read the step's `agent:` frontmatter field.
|
|
486
496
|
- If the step has an `agent:` value present AND it is in `skipped_agents` →
|
|
487
497
|
announce `⏭️ Skipping {Agent Name} (deselected for this run)` and skip this
|
|
488
|
-
step ENTIRELY: no
|
|
489
|
-
validation, no veto, no output file
|
|
498
|
+
step ENTIRELY: no Escritório command, no input validation, no execution, no output
|
|
499
|
+
validation, no veto, no output file. Advance to the next step in
|
|
490
500
|
`filtered_steps`.
|
|
491
501
|
- Checkpoints that declare `agent:` and whose agent was deselected are skipped the
|
|
492
502
|
same way. Checkpoints with no `agent:` field always run (backward compatible).
|
|
493
503
|
- When the selection step was skipped (no `agent_dependencies:`), `skipped_agents`
|
|
494
504
|
is empty → this check never fires (legacy behavior).
|
|
495
505
|
|
|
496
|
-
0b. **
|
|
497
|
-
```json
|
|
498
|
-
{
|
|
499
|
-
"crew": "{crew code from crew.yaml}",
|
|
500
|
-
"status": "running",
|
|
501
|
-
"step": {
|
|
502
|
-
"current": {1-based index of this step},
|
|
503
|
-
"total": {total steps in pipeline},
|
|
504
|
-
"label": "{step id or label}"
|
|
505
|
-
},
|
|
506
|
-
"agents": [
|
|
507
|
-
{
|
|
508
|
-
"id": "{agent id}",
|
|
509
|
-
"name": "{agent displayName}",
|
|
510
|
-
"icon": "{agent icon}",
|
|
511
|
-
"status": "{working if this is the current step's agent, done if already completed, skipped if in skipped_agents, idle otherwise}",
|
|
512
|
-
"desk": {preserve existing desk positions from state.json — do not change col/row}
|
|
513
|
-
}
|
|
514
|
-
],
|
|
515
|
-
"handoff": {preserve existing handoff object, or null if this is the first step},
|
|
516
|
-
"startedAt": "{ISO timestamp — set on the first step only, then preserve from existing state.json on subsequent steps}",
|
|
517
|
-
"updatedAt": "{ISO timestamp now}"
|
|
518
|
-
}
|
|
519
|
-
```
|
|
506
|
+
0b. **Escritório** — if it is on, run `passo`, or `checkpoint` when the step is a checkpoint (see "Escritório" above).
|
|
520
507
|
|
|
521
508
|
1. **Pre-Step Input Validation** — MANDATORY. If the step's frontmatter declares an `inputFile`, validate that the input exists before executing the step. Run via Bash tool:
|
|
522
509
|
```bash
|
|
@@ -732,52 +719,23 @@ When a step has `on_reject: {step-id}` (a review step):
|
|
|
732
719
|
of alerts and the {Z} items not measured or not verified (the `Não medido` and `Não verificado`
|
|
733
720
|
lines under each file, not the "não é texto" line of **Notas**), one per line as
|
|
734
721
|
`{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)
|
|
722
|
+
and repeat every "não rodou" warning of this run (checker and source check) and the line of
|
|
723
|
+
every file left unchecked by the safe-name rule. If the approved
|
|
736
724
|
text still contains `[PREENCHER: …]`, ask the user for each missing piece of real information
|
|
737
725
|
and write it into the text before approving.
|
|
738
726
|
|
|
739
|
-
### Dashboard Handoff (between steps)
|
|
740
|
-
|
|
741
|
-
Only if `dashboard_enabled` (otherwise skip this entire section). After a step
|
|
742
|
-
completes output and there IS a next step:
|
|
743
|
-
|
|
744
|
-
1. **Write delivering state** — Write `crews/{name}/state.json` with:
|
|
745
|
-
- Current step's agent: `"status": "delivering"`
|
|
746
|
-
- Next step's agent: `"status": "idle"`
|
|
747
|
-
- All other agents unchanged
|
|
748
|
-
- Pipeline `"status": "running"`
|
|
749
|
-
- Add or update `"handoff"`:
|
|
750
|
-
```json
|
|
751
|
-
"handoff": {
|
|
752
|
-
"from": "{current agent id}",
|
|
753
|
-
"to": "{next agent id}",
|
|
754
|
-
"message": "{one-sentence summary of what was produced, written in the user's language}",
|
|
755
|
-
"completedAt": "{ISO timestamp now}"
|
|
756
|
-
}
|
|
757
|
-
```
|
|
758
|
-
- `"updatedAt"`: now
|
|
759
|
-
|
|
760
|
-
2. _(No delay — proceed immediately to working state)_
|
|
761
|
-
|
|
762
|
-
2. **Write working state** — Write `crews/{name}/state.json` again with:
|
|
763
|
-
- Current agent: `"status": "done"`
|
|
764
|
-
- Next agent: `"status": "working"`
|
|
765
|
-
- Keep the `"handoff"` object from step 1 unchanged
|
|
766
|
-
- `"updatedAt"`: now
|
|
767
|
-
|
|
768
727
|
### Step Execution Order (Summary)
|
|
769
728
|
|
|
770
729
|
For reference, the complete execution order for each pipeline step is:
|
|
771
730
|
|
|
772
731
|
```
|
|
773
732
|
0. Agent deselection check (skip step if its agent was deselected)
|
|
774
|
-
0b.
|
|
733
|
+
0b. Escritório command (passo or checkpoint) — only if it is on
|
|
775
734
|
1. Pre-Step Input Validation (bash gate)
|
|
776
735
|
2. Read step file
|
|
777
736
|
3. Check execution mode and execute (subagent / inline / checkpoint)
|
|
778
737
|
4. Post-Step Output Validation (bash gate)
|
|
779
738
|
5. Veto Condition Enforcement
|
|
780
|
-
6. Dashboard Handoff (to next step) — only if dashboard_enabled
|
|
781
739
|
```
|
|
782
740
|
|
|
783
741
|
Steps 1 and 4 are binary bash gates. If either fails, the pipeline does NOT advance — the user is consulted.
|
|
@@ -786,31 +744,9 @@ Steps 1 and 4 are binary bash gates. If either fails, the pipeline does NOT adva
|
|
|
786
744
|
|
|
787
745
|
1. Save final output to `crews/{name}/output/{run_id}/{filename}.md`
|
|
788
746
|
(The run folder was created during initialization — no separate date subfolder needed)
|
|
789
|
-
1b. **
|
|
790
|
-
- `"status": "completed"`
|
|
791
|
-
- All agents: `"status": "done"`
|
|
792
|
-
- `"updatedAt"`: now
|
|
793
|
-
- `"completedAt"`: now
|
|
794
|
-
- `"startedAt"`: preserve from existing `state.json`
|
|
795
|
-
- Keep existing `"handoff"` object
|
|
796
|
-
|
|
797
|
-
### Post-Completion Cleanup (only if `dashboard_enabled`)
|
|
798
|
-
|
|
799
|
-
After writing the final "completed" state to `crews/{name}/state.json`:
|
|
800
|
-
|
|
801
|
-
1. Add the `completedAt` field (or `failedAt` if status is `failed`) with the current ISO timestamp
|
|
802
|
-
2. Copy `state.json` to the run output folder for permanent history:
|
|
803
|
-
```bash
|
|
804
|
-
cp crews/{name}/state.json crews/{name}/output/{run_id}/state.json
|
|
805
|
-
```
|
|
806
|
-
3. Leave the working copy of `crews/{name}/state.json` in place — do not delete it and
|
|
807
|
-
do not add an artificial delay. A dashboard watching the file already sees the
|
|
808
|
-
"completed" status the moment it's written; the next run's initialization (step 6)
|
|
809
|
-
overwrites this file from scratch. There is nothing to clean up.
|
|
810
|
-
|
|
811
|
-
This archives the run state for the `runs` command while keeping crew history available.
|
|
747
|
+
1b. **Escritório** — if it is on, run `concluir` (see "Escritório" above).
|
|
812
748
|
|
|
813
|
-
2. **Update crew memory** — write to BOTH files
|
|
749
|
+
2. **Update crew memory** — write to BOTH files:
|
|
814
750
|
|
|
815
751
|
### 2a. Update `memories.md` (living preferences)
|
|
816
752
|
|
|
@@ -927,6 +863,7 @@ This archives the run state for the `runs` command while keeping crew history av
|
|
|
927
863
|
- If a step file is missing, inform the user and suggest running `/opencrew edit {crew}` to fix.
|
|
928
864
|
- If company.md is empty, stop and redirect to onboarding.
|
|
929
865
|
- Never continue past a checkpoint without user input.
|
|
866
|
+
- When the run is aborted: if the Escritório is on, run `falhar` (see "Escritório" above).
|
|
930
867
|
|
|
931
868
|
## Pipeline State
|
|
932
869
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Validações e mensagens de erro de uso comuns aos scripts do runtime (verificar,
|
|
2
2
|
// conferir-fontes…). Node puro, sem dependências.
|
|
3
|
-
//
|
|
3
|
+
// Specs: specs/fase-r1-reparos-1-6-1.md, regra 13, e specs/fase-r2-update-e-envio-seguros.md,
|
|
4
|
+
// regra 23 (repositório do OpenCrew).
|
|
4
5
|
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -12,12 +13,56 @@ export const MSG = {
|
|
|
12
13
|
crewNaoEncontrada: (crew) => `Crew não encontrada: ${crew}`,
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
/**
|
|
16
|
-
export
|
|
17
|
-
|
|
16
|
+
/** Caminho de rede: começa por duas barras (`\\` ou `//`), em qualquer sistema. Nunca vai ao disco. */
|
|
17
|
+
export const ehDeRede = (caminho) => /^[\\/]{2}/.test(caminho);
|
|
18
|
+
|
|
19
|
+
/** Pelo texto: `alvo` é a `pasta` ou fica dentro dela? */
|
|
20
|
+
function contem(pasta, alvo) {
|
|
21
|
+
const rel = path.relative(pasta, alvo);
|
|
18
22
|
return !(rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel));
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Lugar real de um caminho: link, junção e nome curto resolvidos. O trecho que ainda não existe é
|
|
27
|
+
* juntado, como foi escrito, à pasta mais funda que existe. Caminho de rede não é consultado: vale
|
|
28
|
+
* o texto.
|
|
29
|
+
*/
|
|
30
|
+
export function lugarReal(caminho) {
|
|
31
|
+
const abs = path.resolve(caminho);
|
|
32
|
+
if (ehDeRede(caminho) || ehDeRede(abs)) return abs;
|
|
33
|
+
try {
|
|
34
|
+
return realpathSync.native(abs);
|
|
35
|
+
} catch {
|
|
36
|
+
const pai = path.dirname(abs);
|
|
37
|
+
return pai === abs ? abs : path.join(lugarReal(pai), path.basename(abs));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Pelo lugar real: `caminho` é a `pasta` ou fica dentro dela? */
|
|
42
|
+
export const realDentroDe = (pasta, caminho) => contem(lugarReal(pasta), lugarReal(caminho));
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* O caminho (relativo à raiz ou absoluto) fica dentro do projeto? Sim quando o texto ou o lugar
|
|
46
|
+
* real diz "dentro"; não, só quando os dois dizem "fora" (regra 23). Raiz e caminho são resolvidos
|
|
47
|
+
* pela mesma função, e o lugar real só é consultado quando o texto diz "fora". Caminho de rede só
|
|
48
|
+
* vale pelo texto, com o projeto também na rede: o disco não é tocado, em nenhum sistema.
|
|
49
|
+
*/
|
|
50
|
+
export function dentroDoProjeto(raiz, caminho) {
|
|
51
|
+
const alvo = path.resolve(raiz, caminho);
|
|
52
|
+
if (ehDeRede(caminho)) return ehDeRede(raiz) && contem(raiz, alvo);
|
|
53
|
+
return contem(raiz, alvo) || realDentroDe(raiz, alvo);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Caminho de dentro do projeto, relativo à raiz e com `/`: pelo texto quando o texto já fica
|
|
58
|
+
* dentro; senão, pelo lugar real (link, junção ou nome curto que leva ao projeto).
|
|
59
|
+
*/
|
|
60
|
+
export function relativoAoProjeto(raiz, caminho) {
|
|
61
|
+
const alvo = path.resolve(raiz, caminho);
|
|
62
|
+
const [de, para] = contem(raiz, alvo) ? [raiz, alvo] : [lugarReal(raiz), lugarReal(alvo)];
|
|
63
|
+
return path.relative(de, para).split(path.sep).join('/');
|
|
64
|
+
}
|
|
65
|
+
|
|
21
66
|
/**
|
|
22
67
|
* O script foi chamado direto (`node …/script.mjs`)? Compara os caminhos reais: com o projeto
|
|
23
68
|
* aberto por uma junção ou um link de pasta, o Node resolve o link em `import.meta.url` e não em
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// Busca da conferência de fontes: onde está cada caminho citado (crew → raiz do projeto →
|
|
2
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
|
-
//
|
|
3
|
+
// com o mesmo nome. Só testa existência e lista nomes; nunca lê conteúdo nem acessa a rede.
|
|
4
|
+
// Specs: specs/fase-r1-reparos-1-6-1.md, regra 17, e specs/fase-r2-update-e-envio-seguros.md,
|
|
5
|
+
// regra 25 (repositório do OpenCrew).
|
|
5
6
|
import { readdir } from 'node:fs/promises';
|
|
6
|
-
import { existsSync } from 'node:fs';
|
|
7
|
+
import { existsSync, statSync } from 'node:fs';
|
|
7
8
|
import path from 'node:path';
|
|
9
|
+
import { ehDeRede } from '../comum.mjs';
|
|
8
10
|
|
|
9
11
|
export const LIMITE_DA_BUSCA = 20000;
|
|
10
12
|
const IGNORAR = new Set(['node_modules', 'output', '_opencrew', '_build']);
|
|
@@ -13,6 +15,7 @@ export const barra = (p) => p.split(path.sep).join('/');
|
|
|
13
15
|
export const ehAbsoluto = (p) => /^[A-Za-z]:[\\/]/.test(p) || p.startsWith('/');
|
|
14
16
|
export const temBarraFinal = (ref) => /[\\/]$/.test(ref);
|
|
15
17
|
const semBarraFinal = (ref) => ref.replace(/[\\/]+$/, '');
|
|
18
|
+
const ehPasta = (p) => Boolean(p) && Boolean(statSync(p, { throwIfNoEntry: false })?.isDirectory());
|
|
16
19
|
|
|
17
20
|
const SUFIXO_DE_AGENTE = '.agent.md';
|
|
18
21
|
|
|
@@ -33,8 +36,10 @@ function pastasDeQuemCita(raiz, crew, citadoEm) {
|
|
|
33
36
|
/**
|
|
34
37
|
* Caminho real do que foi citado, ou null quando não existe. Ordem: pasta da crew → raiz do
|
|
35
38
|
* projeto → absoluto → pastas ao lado dos arquivos de agente ou de task que citam (`citadoEm`).
|
|
39
|
+
* Citação de duas barras (caminho de rede) nunca chega ao disco: sai como "não existe".
|
|
36
40
|
*/
|
|
37
41
|
export function resolver(raiz, crew, ref, citadoEm = []) {
|
|
42
|
+
if (ehDeRede(ref)) return null;
|
|
38
43
|
if (ehAbsoluto(ref)) return existsSync(ref) ? path.resolve(ref) : null;
|
|
39
44
|
for (const base of [path.resolve(raiz, crew), raiz, ...pastasDeQuemCita(raiz, crew, citadoEm)]) {
|
|
40
45
|
const p = path.resolve(base, ref);
|
|
@@ -43,6 +48,40 @@ export function resolver(raiz, crew, ref, citadoEm = []) {
|
|
|
43
48
|
return null;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
/**
|
|
52
|
+
* O primeiro segmento de `exemplo.com/blog/`, quando tem cara de domínio: um ponto que não é o
|
|
53
|
+
* início e, depois do último ponto, 2 a 24 letras. Nome sem barra (`briefing.md`) é arquivo: null.
|
|
54
|
+
*/
|
|
55
|
+
function dominioNoInicio(ref) {
|
|
56
|
+
const corte = ref.search(/[\\/]/);
|
|
57
|
+
const primeiro = ref.slice(0, Math.max(corte, 0));
|
|
58
|
+
const ponto = primeiro.lastIndexOf('.');
|
|
59
|
+
return ponto > 0 && /^[A-Za-z]{2,24}$/.test(primeiro.slice(ponto + 1)) ? primeiro : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Citação que a conferência não testa (regra 25 da fase R2): caminho de rede (começa por duas
|
|
64
|
+
* barras) ou endereço de site — tem `://` (menos `http(s)://`, que fica como sempre foi), começa
|
|
65
|
+
* por `mailto:` ou `www.`. Caminho de disco (letra de unidade ou uma barra no início) nunca é
|
|
66
|
+
* endereço. Não consulta o disco nem a rede.
|
|
67
|
+
*/
|
|
68
|
+
export function ehRedeOuSite(ref) {
|
|
69
|
+
if (ehDeRede(ref)) return true;
|
|
70
|
+
if (ehAbsoluto(ref) || /^https?:\/\//i.test(ref)) return false;
|
|
71
|
+
return ref.includes('://') || /^(?:mailto:|www\.)/i.test(ref);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Citação que só parece endereço de site pela forma: o primeiro segmento tem cara de domínio e
|
|
76
|
+
* não há pasta com esse nome no projeto. Quem chama confere antes se o projeto tem arquivo com o
|
|
77
|
+
* mesmo nome: pasta com ponto que foi movida continua pendência, com sugestão.
|
|
78
|
+
*/
|
|
79
|
+
export function pareceSite({ raiz, crew }, { ref, citadoEm }) {
|
|
80
|
+
if (ehAbsoluto(ref) || /^https?:\/\//i.test(ref)) return false;
|
|
81
|
+
const dominio = dominioNoInicio(ref);
|
|
82
|
+
return dominio != null && !ehPasta(resolver(raiz, crew, dominio, citadoEm));
|
|
83
|
+
}
|
|
84
|
+
|
|
46
85
|
/**
|
|
47
86
|
* Índice nome → caminhos relativos à raiz (pasta termina em `/`), sem saídas, dependências e
|
|
48
87
|
* pastas ocultas. Para ao passar de `limite` itens: aí `parcial` é true.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Relatório da conferência de fontes e as mensagens ao usuário (PT-BR).
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import {
|
|
2
|
+
// Specs: specs/fase-r1-reparos-1-6-1.md, regra 17 e seção 6, e
|
|
3
|
+
// specs/fase-r2-update-e-envio-seguros.md, regras 24 a 26 e seção 6 (repositório do OpenCrew).
|
|
4
|
+
import { relativoAoProjeto } from '../comum.mjs';
|
|
5
5
|
|
|
6
6
|
const plural = (n, um, varios) => `${n} ${n === 1 ? um : varios}`;
|
|
7
7
|
|
|
@@ -10,6 +10,9 @@ export const MSG = {
|
|
|
10
10
|
corrigidos: (n) => `${plural(n, 'caminho corrigido', 'caminhos corrigidos')} (cópia .bak ao lado de cada arquivo alterado).\n`,
|
|
11
11
|
semCorrecaoAutomatica: (n) => `Não há correção automática para ${n} pendência(s): escolha um candidato ou corrija o caminho na crew.`,
|
|
12
12
|
buscaParcial: (limite) => `Procurei só nos primeiros ${limite} itens do projeto; pode existir um arquivo com esse nome que eu não vi.`,
|
|
13
|
+
linkParaFora: (arquivo) => `Não corrigi \`${arquivo}\`: é um link que aponta para fora da crew. O caminho citado nele continua como estava.`,
|
|
14
|
+
crewLigadaParaFora: (crew) => `Não corrigi nada: a pasta \`${crew}\` é um link que aponta para fora do projeto.`,
|
|
15
|
+
naoConferi: (motivo) => `Não consegui conferir: ${motivo}`,
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
const MAX_NOMES = 20;
|
|
@@ -37,16 +40,25 @@ function linhaDoAlerta(i, onde) {
|
|
|
37
40
|
return `- ⚠️ \`${i.ref}\` é um caminho absoluto (não é portátil — quebra em outro computador).${sugestao} (${onde})`;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
/** Caminho de rede ou endereço de site: a citação aparece, e o relatório diz que não foi testada. */
|
|
44
|
+
function linhaDoNaoConferido(i, onde) {
|
|
45
|
+
return `- ⚠️ \`${i.ref}\` é um caminho de rede ou um endereço de site: não conferi se existe (a conferência não acessa a rede). (${onde})`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Os dois estados de alerta (não mudam o status); qualquer outro estado apontado é pendência.
|
|
49
|
+
const LINHA_DO_ALERTA = { 'nao-portatil': linhaDoAlerta, 'nao-conferido': linhaDoNaoConferido };
|
|
50
|
+
|
|
40
51
|
export function formatar(r) {
|
|
41
52
|
const aviso = r.buscaParcial ? MSG.buscaParcial(r.limite) : '';
|
|
42
53
|
const contar = (estado) => r.refs.filter((i) => i.estado === estado).length;
|
|
43
54
|
const apontados = r.refs.filter((x) => x.estado !== 'ok');
|
|
44
55
|
const linhas = [`## Conferência de fontes — ${r.crew}`, ''];
|
|
45
56
|
for (const i of apontados) {
|
|
46
|
-
const onde = `citado em ${i.citadoEm.map((a) =>
|
|
47
|
-
linhas.push(i.estado
|
|
57
|
+
const onde = `citado em ${i.citadoEm.map((a) => relativoAoProjeto(r.raiz, a)).join(', ')}`;
|
|
58
|
+
linhas.push((LINHA_DO_ALERTA[i.estado] ?? linhaDaPendencia)(i, onde, aviso));
|
|
48
59
|
}
|
|
49
60
|
if (apontados.length) linhas.push(''); // sem pendência nem alerta, uma linha em branco só
|
|
50
|
-
|
|
61
|
+
const alertas = contar('nao-portatil') + contar('nao-conferido');
|
|
62
|
+
linhas.push(`**Resumo: ${r.refs.length} fontes — ${contar('ok')} ok, ${contar('faltando')} pendentes, ${alertas} alertas**`, '');
|
|
51
63
|
return linhas.join('\n');
|
|
52
64
|
}
|