@aksp/opencrew 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +52 -2
  3. package/package.json +1 -1
  4. package/templates/AGENTS.md +3 -0
  5. package/templates/_opencrew/.opencrew-version +1 -1
  6. package/templates/_opencrew/core/architect.agent.yaml +1 -1
  7. package/templates/_opencrew/core/prompts/discovery.prompt.md +1 -1
  8. package/templates/_opencrew/core/prompts/entrega.prompt.md +131 -0
  9. package/templates/_opencrew/core/runner.pipeline.md +91 -98
  10. package/templates/_opencrew/core/scripts/caminho/argumentos.mjs +49 -0
  11. package/templates/_opencrew/core/scripts/caminho/disco.mjs +36 -0
  12. package/templates/_opencrew/core/scripts/caminho/nucleo.mjs +61 -0
  13. package/templates/_opencrew/core/scripts/caminho.mjs +124 -0
  14. package/templates/_opencrew/core/scripts/entrega/argumentos.mjs +46 -0
  15. package/templates/_opencrew/core/scripts/entrega/canais.mjs +51 -0
  16. package/templates/_opencrew/core/scripts/entrega/fora.mjs +49 -0
  17. package/templates/_opencrew/core/scripts/entrega/gravar.mjs +83 -0
  18. package/templates/_opencrew/core/scripts/entrega/leiame.mjs +89 -0
  19. package/templates/_opencrew/core/scripts/entrega/leitor.mjs +120 -0
  20. package/templates/_opencrew/core/scripts/entrega/longas.mjs +109 -0
  21. package/templates/_opencrew/core/scripts/entrega/nomes.mjs +78 -0
  22. package/templates/_opencrew/core/scripts/entrega/passos.mjs +99 -0
  23. package/templates/_opencrew/core/scripts/entrega/pendencias.mjs +87 -0
  24. package/templates/_opencrew/core/scripts/entrega/separar.mjs +103 -0
  25. package/templates/_opencrew/core/scripts/entrega/texto.mjs +95 -0
  26. package/templates/_opencrew/core/scripts/entregar.mjs +141 -0
  27. package/templates/_opencrew/core/scripts/verificar/pecas.mjs +4 -4
  28. package/templates/_opencrew/core/scripts/verificar/secoes.mjs +22 -2
  29. package/templates/_opencrew/core/scripts/verificar.mjs +4 -2
@@ -53,12 +53,9 @@ Before starting execution:
53
53
  > unless the user base expands beyond PT-BR — at that point, discuss a migration strategy
54
54
  > (e.g. i18n key mapping) rather than mixing languages in a single file.
55
55
 
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:
57
- ```bash
58
- [ -f "crews/{name}/_memory/memories.md" ] && grep -q "## Estilo de Escrita" "crews/{name}/_memory/memories.md" && echo "NEW_FORMAT" || echo "OLD_FORMAT"
59
- ```
60
- - If `NEW_FORMAT` → proceed normally.
61
- - If `OLD_FORMAT` (or file is empty / does not exist) → migrate before proceeding:
56
+ 1b. **Memory format migration** — After loading `memories.md`, check whether it uses the new format: it does when it has the `## Estilo de Escrita` section header (read the file with the read tool — no command).
57
+ - If it has the header → proceed normally.
58
+ - If it does not (or the file is empty / does not exist) → migrate before proceeding:
62
59
  a0. If the file exists and is not empty, FIRST copy it to `crews/{name}/_memory/memories.md.bak`
63
60
  (never lose what the crew learned), then tell the user in one line:
64
61
  "Atualizei o formato da memória da crew; a versão anterior está em `memories.md.bak`."
@@ -78,11 +75,8 @@ Before starting execution:
78
75
  ## Técnico (específico do crew)
79
76
  ```
80
77
  (Use the crew's display name for `{crew-name}`, and the crew code for `{name}` in file paths — they refer to the same crew.)
81
- b. Check if `crews/{name}/_memory/runs.md` exists:
82
- ```bash
83
- test -f "crews/{name}/_memory/runs.md" && echo "EXISTS" || echo "MISSING"
84
- ```
85
- If `MISSING`, create it with:
78
+ b. Check if `crews/{name}/_memory/runs.md` exists (read tool — no command).
79
+ If it does not exist, create it with:
86
80
  ```markdown
87
81
  # Run History: {crew-name}
88
82
 
@@ -229,9 +223,9 @@ Before starting execution:
229
223
  identical to today: all agents listed, no Skipped line.
230
224
  5b. **Initialize run folder**: Generate a unique run ID for this execution:
231
225
  - Format: `YYYY-MM-DD-HHmmss` using the current timestamp (e.g. `2026-03-03-143022`)
232
- - Check if `crews/{name}/output/{run_id}/` already exists
226
+ - Check (folder-listing tool, no command) if `crews/{name}/output/{run_id}/` already exists
233
227
  - If it does (sub-second collision), append `-2`, `-3`, etc. until the folder does not exist
234
- - Create the folder using Bash: `mkdir -p "crews/{name}/output/{run_id}"`
228
+ - Create the folder: run the `pasta` command (see "Output Path Transformation" below) — never create a folder by command yourself
235
229
  - Store `run_id` in working memory for this run — it will be used for ALL output paths
236
230
  6. **Escritório** — if it is on, run `iniciar`, then one `pular` per deselected agent, one after the other (see "Escritório" below).
237
231
 
@@ -379,11 +373,9 @@ Before executing any step that references an agent:
379
373
  To prevent linear token growth across multi-agent pipelines, apply context compression
380
374
  when passing prior agents' outputs as context:
381
375
 
382
- 1. **TL;DR extraction**: After each agent completes, check if its output contains a `## TL;DR` section.
376
+ 1. **TL;DR extraction**: After each agent completes, check if its output contains a `## TL;DR` section
377
+ (a line starting with `## TL;DR` — you have the output, no command is needed).
383
378
  If present, extract and store it separately as the agent's summary.
384
- ```bash
385
- grep -q "^## TL;DR" "{outputFile}" && echo "HAS_TLDR" || echo "NO_TLDR"
386
- ```
387
379
 
388
380
  2. **Compressed context assembly**: When preparing context for Agent N:
389
381
  - Include **TL;DR summaries** from Agents 1 through N-2 (all agents except the direct predecessor)
@@ -441,7 +433,7 @@ When an agent's `.agent.md` frontmatter contains a `tasks:` field:
441
433
  e. Check task veto conditions (same enforcement as step veto conditions below)
442
434
 
443
435
  3. **Final output**: The output of the LAST task in the chain becomes the step's output
444
- - Apply the Output Path Transformation (Steps 1 and 2: run_id injection + version folder) to the `outputFile` path before saving — this applies regardless of whether the step runs as `execution: inline` or `execution: subagent`
436
+ - Resolve the `outputFile` path with the `saida` command (Output Path Transformation) before saving — this applies regardless of whether the step runs as `execution: inline` or `execution: subagent`
445
437
  - Save to the **transformed** outputFile path
446
438
  - This is what the next step (or checkpoint) receives
447
439
 
@@ -455,40 +447,44 @@ When an agent's `.agent.md` frontmatter contains a `tasks:` field:
455
447
 
456
448
  ### Output Path Transformation
457
449
 
458
- Before saving any output file in a step, apply these rules to determine the final path:
459
-
460
- #### Step 1 — Insert run_id
461
-
462
- - If the path starts with `crews/{name}/output/`, insert `{run_id}/` immediately after `output/`
463
- - Example: `crews/carousel/output/slides/draft.md` → `crews/carousel/output/2026-03-03-143022/slides/draft.md`
464
- - Example: `crews/carousel/output/angles-brief.yaml` → `crews/carousel/output/2026-03-03-143022/angles-brief.yaml`
465
- - If the path does NOT start with `crews/{name}/output/`, leave it unchanged
466
-
467
- #### Step 2 — Insert version folder
468
-
469
- Apply to every path that was transformed in Step 1:
470
-
471
- 1. Determine the **output group** = the parent directory of the file (after Step 1 transformation)
472
- - Example: `crews/carousel/output/2026-03-03-143022/slides/draft.md` → group is `crews/carousel/output/2026-03-03-143022/slides/`
473
- - Example: `crews/carousel/output/2026-03-03-143022/angles-brief.yaml` → group is `crews/carousel/output/2026-03-03-143022/`
474
-
475
- 2. Detect existing versions for this group using Bash:
476
- ```bash
477
- ls -1 "crews/{name}/output/{run_id}/{relative-group}/" 2>/dev/null | grep -E '^v[0-9]+$' | sort -V | tail -1
478
- ```
479
- - If the command returns a version (e.g. `v2`) → use `v3`
480
- (Always increment the highest version found, even if lower versions have gaps — e.g. if `v1` and `v3` exist, use `v4`)
481
- - If the command returns nothing (no versions yet) → use `v1`
482
- (`{relative-group}` is the portion of the group path after `crews/{name}/output/{run_id}/`, e.g. `slides/` or empty string for root-level files)
483
-
484
- 3. Insert the version folder immediately before the filename:
485
- - `crews/carousel/output/2026-03-03-143022/slides/draft.md` → `crews/carousel/output/2026-03-03-143022/slides/v1/draft.md`
486
- - `crews/carousel/output/2026-03-03-143022/angles-brief.yaml` → `crews/carousel/output/2026-03-03-143022/v1/angles-brief.yaml`
487
-
488
- 4. **Cache per group**: within a single step execution, once a version is determined for a group, reuse it for all subsequent files in that same group. Do not re-run the `ls` per file.
489
- If the same file path is written twice within a step, both writes go to the same versioned path (the second write overwrites the first within that version).
450
+ The path of every file of the run comes from one script (`caminho.mjs`), the same on every system —
451
+ never from a path you put together, never from a shell command of your own. Run from the project
452
+ root the one-line command of each moment and read the last line (`CAMINHO:OK {path}`,
453
+ `CAMINHO:FALTA {path}` or `CAMINHO:REPROVADO {motivo}`):
490
454
 
491
- Apply this transformation consistently for every write in this step.
455
+ | Moment | Command |
456
+ |---|---|
457
+ | Start of the run (Initialization, step 5b) | `node _opencrew/core/scripts/caminho.mjs "{name}" pasta --run "{run_id}"` |
458
+ | Before a step, for its `inputFile` | `node _opencrew/core/scripts/caminho.mjs "{name}" entrada --run "{run_id}" --arquivo "{inputFile}"` |
459
+ | Before a step writes, for the first `outputFile` of each group | `node _opencrew/core/scripts/caminho.mjs "{name}" saida --run "{run_id}" --arquivo "{outputFile}"` |
460
+ | After a step wrote, for each output file | `node _opencrew/core/scripts/caminho.mjs "{name}" conferir --arquivo "{path}"` |
461
+
462
+ - **Values** — `{name}`: the crew code. `{inputFile}` / `{outputFile}`: the path as the step
463
+ declares it (raw, without the run_id). `{path}`: the path `saida` returned. The safe-name rule
464
+ (nome seguro) applies: the crew and every path between double quotes.
465
+ - **`saida`** answers with the **transformed** path and creates its folder: write the file there,
466
+ never to the raw path. Run it once per group in a step — the other `outputFile`s of the same
467
+ group reuse the version folder it returned, and a file written twice in a step goes to the same path.
468
+ - **`entrada`** answers with the newest output of that file: use the path it returns, whatever
469
+ its version folder.
470
+ - **The rule the script applies** (apply it yourself only when the script does not run):
471
+ 1. A declared path that starts with `crews/{name}/output/` gets `{run_id}/` right after
472
+ `output/`; any other path stays as declared, with no version folder.
473
+ 2. The **group** is the folder of the file, run_id included (`…/output/{run_id}/`, or
474
+ `…/output/{run_id}/slides/`). A step writes to the group's next version folder: the highest
475
+ `vN` there plus 1, or `v1` when there is none — numeric order (`v10` comes after `v9`), gaps
476
+ not filled (`v1` and `v3` → `v4`).
477
+ 3. A step reads the newest version that has the file: from the highest `vN` down, the first
478
+ where the file exists and is not empty; then the group itself, with no version folder (where
479
+ checkpoint answers live).
480
+
481
+ Example, one group: the researcher writes `…/v1/pesquisa.md`, the writer writes `…/v2/post.md`
482
+ and reads `…/v1/pesquisa.md`. Never assume `v1`.
483
+ - **Script that does not run** (no Node, an error, or no `CAMINHO:` line): tell the user once per
484
+ run `Não consegui rodar a conferência de caminhos; sigo pela regra escrita e marco os arquivos como não verificados.`,
485
+ build the path by the rule above (the Write tool creates the folder) and continue. A file handled
486
+ this way skips its gate and is listed at the final approval:
487
+ `{arquivo} — não verificado: a conferência de caminhos não rodou`.
492
488
 
493
489
  ### For each pipeline step:
494
490
 
@@ -504,14 +500,11 @@ Apply this transformation consistently for every write in this step.
504
500
  is empty → this check never fires (legacy behavior).
505
501
 
506
502
  0b. **Escritório** — if it is on, run `passo`, or `checkpoint` when the step is a checkpoint (see "Escritório" above).
503
+ 0c. **Entrega** — before the first step that publishes or sends, once the final approval was given, run the delivery (see "Entrega" below).
507
504
 
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:
509
- ```bash
510
- test -s "{transformed inputFile path}" && echo "VALIDATION:PASS" || echo "VALIDATION:FAIL"
511
- ```
512
- - Apply the Output Path Transformation (Step 1: run_id injection) to the `inputFile` path before running the check.
513
- - If the Bash output contains `VALIDATION:PASS` → proceed to execute the step.
514
- - If the Bash output contains `VALIDATION:FAIL` → do NOT execute the step. Present to user:
505
+ 1. **Pre-Step Input Validation** — MANDATORY. If the step's frontmatter declares an `inputFile`, the input comes from the `entrada` action, never from a path you build: validate that the input exists before executing the step. Run the `entrada` command (Output Path Transformation) with the `inputFile` as declared:
506
+ - `CAMINHO:OK {path}` → that path is the step's input (the newest version that has the file): read the input from it and execute the step.
507
+ - `CAMINHO:FALTA {path}` → do NOT execute the step. Present to user:
515
508
  ```
516
509
  ⚠️ Input for {Agent Name} not found: {path}
517
510
  The previous step may have failed to produce output.
@@ -530,7 +523,7 @@ Apply this transformation consistently for every write in this step.
530
523
  - Inform user: `🔍 {Agent Name} is working in the background...`
531
524
  - Read the step's `model_tier` frontmatter field (if present).
532
525
  Valid values: `fast` or `powerful`. If absent or any other value: default to `powerful`.
533
- - **Before building the subagent prompt**: Apply the Output Path Transformation (Step 1: run_id injection + Step 2: version folder) to all output paths referenced in the step file. 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.
526
+ - **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.
534
527
  - Use the Task tool to dispatch the step as a subagent:
535
528
  - If `model_tier: fast`: use the fastest/lightest model available in your current IDE.
536
529
  - If `model_tier: powerful` or absent/invalid: use the default model (no model override needed)
@@ -542,7 +535,7 @@ Apply this transformation consistently for every write in this step.
542
535
  - The veto conditions from the step file (agent should self-check before completing)
543
536
  - The company context
544
537
  - The crew memory
545
- - The **transformed** path to save output (e.g., `crews/{name}/output/2026-03-20-140736/slides/v1/draft.md`)
538
+ - The **transformed** path to save output (the one `saida` returned, e.g. `crews/{name}/output/2026-03-20-140736/slides/v2/draft.md`)
546
539
  - Wait for the subagent to complete
547
540
  - Inform user: `✓ {Agent Name} completed`
548
541
  - Proceed to Post-Step Output Validation (below) before advancing.
@@ -552,13 +545,13 @@ Apply this transformation consistently for every write in this step.
552
545
  - Announce: `{icon} {Agent Name} is working...`
553
546
  - Follow the step instructions
554
547
  - Present output directly in the conversation
555
- - Save output to the specified output file — apply the Output Path Transformation (Steps 1 and 2) to the path before writing. Do not write to the raw path from the step file.
548
+ - Save output to the specified output file — resolve the path with the `saida` command (Output Path Transformation) before writing. Do not write to the raw path from the step file.
556
549
  - Proceed to Post-Step Output Validation (below) before advancing.
557
550
 
558
551
  #### If `type: checkpoint`
559
552
  - Present the checkpoint message to the user
560
553
  - If the checkpoint requires a choice (numbered list), present options as a numbered list
561
- - **Always include the file path** of any generated content the user needs to review. Example: "Review the content at `crews/{name}/output/{run_id}/v1/content.md` and let me know if it looks good."
554
+ - **Always include the file path** of any generated content the user needs to review. Example: "Review the content at `crews/{name}/output/{run_id}/v2/content.md` and let me know if it looks good." (the path the script returned)
562
555
  - Wait for user input before proceeding
563
556
  - Save the user's choice/response for the next step
564
557
  - **Correction → memory, right away**: if the answer corrects something (tone, audience, a term,
@@ -571,7 +564,7 @@ Apply this transformation consistently for every write in this step.
571
564
  (e.g. the organization's name, the main audience), ask: "Isso vale para todas as crews?
572
565
  Atualizo o perfil da empresa?" — change `company.md` only after a yes.
573
566
  - **If the step frontmatter contains `outputFile`**: after collecting the user's full response,
574
- apply the Output Path Transformation **Step 1 only** (run_id injection — skip Step 2, version folder) to the `outputFile` path, then write the response to the transformed path using the Write tool before moving to the next step. Checkpoint files are user input captures, not versioned output — Step 2 does not apply here, regardless of the general "every write" rule in the Output Path Transformation section above.
567
+ 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 using the Write 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.
575
568
  Use this format:
576
569
  ```
577
570
  # Research Focus
@@ -584,28 +577,22 @@ Apply this transformation consistently for every write in this step.
584
577
 
585
578
  ### Post-Step Output Validation
586
579
 
587
- After a step produces output (subagent or inline) and BEFORE Veto Condition Enforcement, the runner MUST validate that the declared output files exist and are non-empty. This is a binary, non-negotiable gate — the runner does NOT proceed on memory or assumption, only on bash output.
588
-
589
- **If the step declares an `outputFile`** (single or multiple), run via Bash tool for EACH output file:
590
-
591
- ```bash
592
- test -s "{transformed outputFile path}" && echo "VALIDATION:PASS" || echo "VALIDATION:FAIL"
593
- ```
580
+ After a step produces output (subagent or inline) and BEFORE Veto Condition Enforcement, the runner MUST validate that the declared output files exist and are non-empty. This is a binary, non-negotiable gate — the runner does NOT proceed on memory or assumption, only on the script's `CAMINHO:` line.
594
581
 
595
- Use the **stored transformed path** (after Output Path Transformation Steps 1 and 2), not the raw path from the step file.
582
+ **If the step declares an `outputFile`** (single or multiple), run the `conferir` command (Output Path Transformation) for EACH output file, with the **stored transformed path** (the one `saida` returned), not the raw path from the step file. A step with an `output_contract:` adds its options to this same call (see Output Contract Validation): one command per file.
596
583
 
597
- **Rules:**
598
- - If ALL output files return `VALIDATION:PASS` → proceed to Veto Condition Enforcement.
584
+ **Rules** (`FAIL` below = the last line is `CAMINHO:REPROVADO arquivo ausente ou vazio`):
585
+ - If ALL output files return `CAMINHO:OK` → proceed to Veto Condition Enforcement.
599
586
  - **Irreversible step** (`side_effects: irreversible` — publish, post, send) with ANY
600
- `VALIDATION:FAIL` → NEVER re-execute it. Tell the user: "⚠️ {Agent Name} did not save its
587
+ `FAIL` → NEVER re-execute it. Tell the user: "⚠️ {Agent Name} did not save its
601
588
  output, but the action may already have happened (post published / email sent). Check
602
589
  before retrying." Then offer: 1. Retry step (only after the user checked) · 2. Mark as done
603
590
  and continue · 3. Abort pipeline.
604
- - If ANY output file returns `VALIDATION:FAIL` (any other step):
591
+ - If ANY output file returns `FAIL` (any other step):
605
592
  1. **Retry once**: re-execute the entire step with the same input and context.
606
593
  2. After re-execution, run the validation again for all output files.
607
- 3. If second attempt returns `VALIDATION:PASS` for all files → proceed normally.
608
- 4. If second attempt still has ANY `VALIDATION:FAIL` → present to user:
594
+ 3. If second attempt returns `CAMINHO:OK` for all files → proceed normally.
595
+ 4. If second attempt still has ANY `FAIL` → present to user:
609
596
  ```
610
597
  ⚠️ {Agent Name}'s output was not generated: {path}
611
598
 
@@ -617,26 +604,23 @@ Use the **stored transformed path** (after Output Path Transformation Steps 1 an
617
604
  - If the step does not declare an `outputFile` (e.g., steps that only produce inline console output) → skip output validation.
618
605
  - Checkpoint steps (`type: checkpoint`) are exempt — their output is the user's response, not a file.
619
606
 
620
- **IMPORTANT**: Do NOT rely on reading the file with the Read tool to "verify" output. The Read tool returns content that can be misinterpreted. Use ONLY the bash `test -s` command — its output is binary and cannot be hallucinated.
607
+ **IMPORTANT**: Do NOT rely on reading the file with the Read tool to "verify" output. The Read tool returns content that can be misinterpreted. Use ONLY the `conferir` command — its last line is binary and cannot be hallucinated.
621
608
 
622
609
  ### Output Contract Validation
623
610
 
624
611
  If the step's frontmatter declares an `output_contract:` field, apply structured validation
625
- AFTER the basic file existence check passes:
612
+ in the same call as the basic file existence check (Post-Step Output Validation):
626
613
 
627
- 1. **Required sections check**: If `output_contract.required_sections` is defined,
628
- verify each required section exists in the output file:
629
- ```bash
630
- grep -c "^## " "{transformed outputFile path}" | xargs -I {} test {} -ge {min_sections} && echo "SECTIONS:PASS" || echo "SECTIONS:FAIL"
631
- ```
614
+ 1. **Required sections check**: If `output_contract.required_sections` is defined, add
615
+ `--secoes {min_sections}` to the same `conferir` command: the file needs at least that many
616
+ lines starting with `## `.
632
617
 
633
- 2. **TL;DR check**: If the output contract requires a TL;DR section:
634
- ```bash
635
- grep -q "^## TL;DR" "{transformed outputFile path}" && echo "TLDR:PASS" || echo "TLDR:FAIL"
636
- ```
618
+ 2. **TL;DR check**: If the output contract requires a TL;DR section, add `--tldr` to the same
619
+ `conferir` command.
637
620
 
638
- 3. **If any check fails**:
639
- - Present to user: "⚠️ Output from {Agent Name} is incomplete: {which checks failed}"
621
+ 3. **If a check fails** (the last line is `CAMINHO:REPROVADO {motivo}`, with a motivo other than
622
+ `arquivo ausente ou vazio`; the script reports the first one):
623
+ - Present to user: "⚠️ Output from {Agent Name} is incomplete: {motivo}"
640
624
  - Options as numbered list:
641
625
  1. Accept anyway and continue
642
626
  2. Retry step (re-execute the agent)
@@ -720,7 +704,8 @@ When a step has `on_reject: {step-id}` (a review step):
720
704
  lines under each file, not the "não é texto" line of **Notas**), one per line as
721
705
  `{arquivo} — {motivo}`, then the lines under `**Notas:**` in that report, as they are written,
722
706
  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
707
+ every file left unchecked by the safe-name rule. List the same way every file the path script
708
+ did not check (see Output Path Transformation). If the approved
724
709
  text still contains `[PREENCHER: …]`, ask the user for each missing piece of real information
725
710
  and write it into the text before approving.
726
711
 
@@ -731,19 +716,28 @@ For reference, the complete execution order for each pipeline step is:
731
716
  ```
732
717
  0. Agent deselection check (skip step if its agent was deselected)
733
718
  0b. Escritório command (passo or checkpoint) — only if it is on
734
- 1. Pre-Step Input Validation (bash gate)
719
+ 0c. Entrega (delivery script) — only before the first step that publishes or sends
720
+ 1. Pre-Step Input Validation (script gate: `entrada`)
735
721
  2. Read step file
736
722
  3. Check execution mode and execute (subagent / inline / checkpoint)
737
- 4. Post-Step Output Validation (bash gate)
723
+ 4. Post-Step Output Validation (script gate: `conferir`)
738
724
  5. Veto Condition Enforcement
739
725
  ```
740
726
 
741
- Steps 1 and 4 are binary bash gates. If either fails, the pipeline does NOT advance — the user is consulted.
727
+ Steps 1 and 4 are binary script gates. If either fails, the pipeline does NOT advance — the user is consulted.
728
+
729
+ ### Entrega
730
+
731
+ One script turns the approved files into `crews/{name}/output/{run_id}/entrega/` (a folder per channel, text ready to paste, a `LEIA-ME.md`). Read `_opencrew/core/prompts/entrega.prompt.md` and follow it: how to build `{lista}`, when to add `--vai-publicar`, what to do with `ENTREGA:OK`, with `ENTREGA:INCOMPLETA` and with a script that did not run.
732
+
733
+ - **Command** — from the project root, by the safe-name rule (nome seguro), everything between double quotes: `node _opencrew/core/scripts/entregar.mjs --crew "crews/{name}" --run "{run_id}" --arquivo "{lista}"`
734
+ - **When** — once, after the final approval, immediately before the first step that publishes or sends (`side_effects: irreversible`, in the step or in the agent's skill); with no such step, after the last step. Always before the end-of-run command of the Escritório. If the irreversible step comes before the final approval (a crew built by an old version), the delivery runs at the end.
735
+ - **Crew with no final approval checkpoint** — same moments, and show `Esta crew não tem aprovação final: confira os arquivos antes de usar.` **Never** for a run that was rejected, aborted before the final approval or left with no approved file.
736
+ - The output of the script is the final summary of the run: show it as it came; if the run stops later, at an irreversible step, show it before stopping. After "Edit this content" changes an approved file, run it again.
742
737
 
743
738
  ### After Pipeline Completion
744
739
 
745
- 1. Save final output to `crews/{name}/output/{run_id}/{filename}.md`
746
- (The run folder was created during initialization — no separate date subfolder needed)
740
+ 1. **Entrega** — if the delivery has not run in this run, run it now (see "Entrega" above).
747
741
  1b. **Escritório** — if it is on, run `concluir` (see "Escritório" above).
748
742
 
749
743
  2. **Update crew memory** — write to BOTH files:
@@ -844,8 +838,7 @@ Steps 1 and 4 are binary bash gates. If either fails, the pipeline does NOT adva
844
838
  ```
845
839
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
846
840
  ✅ Pipeline complete!
847
- 📁 Run folder: crews/{name}/output/{run_id}/
848
- 📄 Output saved to: {output path}
841
+ 📁 Delivery: crews/{name}/output/{run_id}/entrega/ — start with LEIA-ME.md
849
842
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
850
843
 
851
844
  What would you like to do?
@@ -0,0 +1,49 @@
1
+ // Linha de comando do `caminho.mjs`: a crew, a ação, as opções e a linha de uso.
2
+ // Spec: fase-r3-runner-em-uso-real.md, §3 e §6 (repositório do OpenCrew).
3
+ import { MSG } from '../comum.mjs';
4
+ import { ACOES } from './nucleo.mjs';
5
+
6
+ export const USO = 'Uso: node _opencrew/core/scripts/caminho.mjs <crew> <ação> --run <id> [opções]';
7
+
8
+ const LISTA = ACOES.join(', ');
9
+ const OPCAO = /^--(run|arquivo|secoes|tldr)(?:=(.*))?$/s;
10
+ const RUN = /^(?!\.+$)[A-Za-z0-9._-]+$/;
11
+ const INTEIRO = /^[1-9]\d{0,8}$/;
12
+
13
+ /** Texto que veio da linha de comando e volta numa mensagem: uma linha só, até 200 caracteres. */
14
+ export const limpar = (valor) => String(valor).replace(/\s+/g, ' ').trim().slice(0, 200);
15
+
16
+ /**
17
+ * `argv` → `{ crew, acao, run, arquivo, secoes, tldr }`. Os dois primeiros argumentos soltos são
18
+ * a crew e a ação. Opção vale como `--nome valor` e `--nome=valor`; `--tldr` não leva valor.
19
+ * Opção ausente fica `undefined`.
20
+ */
21
+ export function lerArgs(argv) {
22
+ const soltos = [];
23
+ const opcoes = {};
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const [, nome, colado] = argv[i].match(OPCAO) ?? [];
26
+ if (!nome) soltos.push(argv[i]);
27
+ else if (nome === 'tldr') opcoes.tldr = true;
28
+ else if (colado !== undefined) opcoes[nome] = colado;
29
+ else opcoes[nome] = i + 1 < argv.length && !OPCAO.test(argv[i + 1]) ? argv[++i] : '';
30
+ }
31
+ const [crew, acao] = soltos.filter((s) => !s.startsWith('--'));
32
+ return { crew, acao, tldr: false, ...opcoes };
33
+ }
34
+
35
+ /**
36
+ * O que falta ou está errado na linha de comando, antes de olhar o disco. `conferir` recebe um
37
+ * caminho já resolvido: é a única ação que não precisa de `--run`.
38
+ * @returns {string|null} o motivo em PT-BR, ou `null`
39
+ */
40
+ export function erroDeArgumentos({ crew, acao, run, arquivo, secoes }) {
41
+ if (!crew) return 'Falta o nome da crew.';
42
+ if (!acao) return `Falta a ação. Ações: ${LISTA}.`;
43
+ if (!ACOES.includes(acao)) return `Ação desconhecida: ${limpar(acao)}. Ações: ${LISTA}.`;
44
+ if (acao !== 'conferir' && !run) return MSG.faltaOpcao('--run');
45
+ if (run !== undefined && !RUN.test(run)) return 'O --run só aceita letras, dígitos, ponto, sublinhado e hífen.';
46
+ if (acao !== 'pasta' && !arquivo) return MSG.faltaOpcao('--arquivo');
47
+ if (secoes !== undefined && !INTEIRO.test(secoes)) return 'O --secoes é um número inteiro a partir de 1.';
48
+ return null;
49
+ }
@@ -0,0 +1,36 @@
1
+ // O que o `caminho.mjs` faz no disco: lê nomes de pastas, confere se um arquivo tem conteúdo, lê
2
+ // um arquivo e cria pastas. Nunca cria, altera nem apaga arquivo.
3
+ // Spec: fase-r3-runner-em-uso-real.md, regra 6 (repositório do OpenCrew).
4
+ import { mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
5
+
6
+ /** Os nomes das pastas que ficam direto em `pasta`; pasta que não existe não tem nenhuma. */
7
+ export function pastasDe(pasta) {
8
+ try {
9
+ return readdirSync(pasta, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
10
+ } catch {
11
+ return [];
12
+ }
13
+ }
14
+
15
+ export function ehPasta(caminho) {
16
+ try {
17
+ return statSync(caminho).isDirectory();
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ /** É um arquivo, existe e não está vazio? */
24
+ export function temConteudo(arquivo) {
25
+ try {
26
+ const info = statSync(arquivo);
27
+ return info.isFile() && info.size > 0;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ export const lerTexto = (arquivo) => readFileSync(arquivo, 'utf8');
34
+
35
+ /** Cria a pasta com as pastas-mãe; pasta que já existe não é erro. */
36
+ export const criarPasta = (pasta) => { mkdirSync(pasta, { recursive: true }); };
@@ -0,0 +1,61 @@
1
+ // Núcleo do `caminho.mjs`: as regras de caminho de uma execução, em funções puras. Não toca em
2
+ // disco nem em processo (quem lê pastas e arquivos é `disco.mjs`).
3
+ // Spec: fase-r3-runner-em-uso-real.md, regras 2 a 5 (repositório do OpenCrew).
4
+
5
+ export const ACOES = ['pasta', 'saida', 'entrada', 'conferir'];
6
+
7
+ /** Os motivos de `CAMINHO:REPROVADO`, na ordem em que são conferidos (§6 da spec). */
8
+ export const MOTIVO = {
9
+ ausente: 'arquivo ausente ou vazio',
10
+ secoes: (achadas, minimo) => `${achadas} seções, mínimo ${minimo}`,
11
+ tldr: 'falta a seção TL;DR',
12
+ };
13
+
14
+ /** Caminho como o script o compara e devolve: com `/`, sem `./` na frente e sem barra dobrada. */
15
+ export const normalizar = (caminho) => String(caminho).replace(/\\/g, '/').replace(/^(?:\.\/+)+/, '').replace(/\/{2,}/g, '/');
16
+
17
+ /**
18
+ * Regra 2 — o caminho da execução. Caminho declarado que começa por `crews/<crew>/output/` ganha
19
+ * `<run>/` logo depois de `output/`.
20
+ * @returns {{ grupo: string, nome: string } | null} o grupo (a pasta do arquivo, já com o run) e
21
+ * o nome do arquivo; `null` quando o caminho não é de `output/` (volta como veio)
22
+ */
23
+ export function naExecucao(declarado, crew, run) {
24
+ const saida = `crews/${crew}/output/`;
25
+ const caminho = normalizar(declarado);
26
+ if (!caminho.startsWith(saida)) return null;
27
+ const partes = caminho.slice(saida.length).split('/');
28
+ const nome = partes.pop();
29
+ return { grupo: [`${saida}${run}`, ...partes].join('/'), nome };
30
+ }
31
+
32
+ /** O número de uma pasta de versão (`v` + número), ou `null` para qualquer outro nome. */
33
+ function numero(nome) {
34
+ const [, digitos] = /^v(\d{1,9})$/.exec(nome) ?? [];
35
+ return digitos === undefined ? null : Number(digitos);
36
+ }
37
+
38
+ /** As pastas de versão, da mais nova para a mais antiga, em ordem numérica (`v10` antes de `v9`). */
39
+ export function daMaisNova(nomes) {
40
+ return nomes.filter((nome) => numero(nome) !== null).sort((a, b) => numero(b) - numero(a));
41
+ }
42
+
43
+ /** Regra 3 — a versão em que o passo grava: a maior `vN` do grupo mais 1; sem nenhuma, `v1`. */
44
+ export function proximaVersao(nomes) {
45
+ const [maior] = daMaisNova(nomes);
46
+ return `v${maior ? numero(maior) + 1 : 1}`;
47
+ }
48
+
49
+ /**
50
+ * Regra 5 — o que falta no texto de um arquivo gravado, pelo primeiro motivo da §6.
51
+ * @param {string} texto o conteúdo do arquivo (que já existe e não está vazio)
52
+ * @param {{ secoes?: number|null, tldr?: boolean }} pedido
53
+ * @returns {string|null} o motivo, ou `null` quando o arquivo passa
54
+ */
55
+ export function motivoDeReprovacao(texto, { secoes = null, tldr = false } = {}) {
56
+ const linhas = (texto.charCodeAt(0) === 0xfeff ? texto.slice(1) : texto).split(/\r?\n/);
57
+ const achadas = linhas.filter((linha) => linha.startsWith('## ')).length;
58
+ if (secoes !== null && achadas < secoes) return MOTIVO.secoes(achadas, secoes);
59
+ if (tldr && !linhas.some((linha) => linha.startsWith('## TL;DR'))) return MOTIVO.tldr;
60
+ return null;
61
+ }
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ // Caminho da execução de uma crew: diz onde cada passo grava, de onde lê e se o arquivo gravado
3
+ // está lá. Quem calcula é este script, igual em qualquer sistema — a IA não monta o caminho.
4
+ // Uso (na pasta do projeto): node _opencrew/core/scripts/caminho.mjs <crew> <ação> --run <id> [opções]
5
+ // pasta --run <id> cria crews/<crew>/output/<id>/
6
+ // saida --run <id> --arquivo <declarado> onde o passo grava (abre a pasta de versão seguinte)
7
+ // entrada --run <id> --arquivo <declarado> a saída mais nova desse arquivo
8
+ // conferir --arquivo <caminho já resolvido> [--secoes N] [--tldr]
9
+ // <crew> é o nome da pasta em `crews/`. <declarado> é o `inputFile` ou `outputFile` do passo.
10
+ // Só cria pastas, e só dentro de crews/<crew>/output/<id>/; nunca cria, altera nem apaga arquivo.
11
+ // Última linha da saída (o runner lê esta linha): CAMINHO:OK <caminho>, CAMINHO:FALTA <caminho>
12
+ // ou CAMINHO:REPROVADO <motivo>; o caminho sai relativo à pasta do projeto, com `/`.
13
+ // Código de saída: 0 sempre que a linha CAMINHO: sai · 1 = erro de uso (ação ou opção faltando,
14
+ // pasta sem `_opencrew/`, crew inexistente ou fora do projeto); com código 1 não há linha
15
+ // CAMINHO: e nada é criado.
16
+ // Spec: fase-r3-runner-em-uso-real.md (repositório do OpenCrew).
17
+ import path from 'node:path';
18
+ import { MSG, dentroDoProjeto, ehPrincipal, realDentroDe } from './comum.mjs';
19
+ import { USO, erroDeArgumentos, lerArgs, limpar } from './caminho/argumentos.mjs';
20
+ import { MOTIVO, daMaisNova, motivoDeReprovacao, naExecucao, normalizar, proximaVersao } from './caminho/nucleo.mjs';
21
+ import { criarPasta, ehPasta, lerTexto, pastasDe, temConteudo } from './caminho/disco.mjs';
22
+
23
+ const ok = (caminho) => `CAMINHO:OK ${caminho}`;
24
+ const falta = (caminho) => `CAMINHO:FALTA ${caminho}`;
25
+ const reprovado = (motivo) => `CAMINHO:REPROVADO ${motivo}`;
26
+
27
+ /**
28
+ * O arquivo declarado cabe na execução? Dentro de `output/`, depois de ganhar o run, ele tem de
29
+ * continuar dentro da pasta da execução — pelo texto e pelo lugar real.
30
+ * @returns {string|null} o erro de uso, ou `null`
31
+ */
32
+ function erroDoArquivo(raiz, crew, { acao, run, arquivo }) {
33
+ if (!dentroDoProjeto(raiz, arquivo)) return MSG.foraDoProjeto(limpar(arquivo));
34
+ const local = acao === 'conferir' ? null : naExecucao(arquivo, crew, run);
35
+ if (!local) return null;
36
+ if (!local.nome) return `Falta o nome do arquivo em --arquivo: ${limpar(arquivo)}`;
37
+ const execucao = path.resolve(raiz, 'crews', crew, 'output', run);
38
+ const partes = normalizar(arquivo).split('/');
39
+ const sai = partes.includes('..') || partes.includes('.') || !realDentroDe(execucao, path.resolve(raiz, local.grupo));
40
+ return sai ? `Caminho fora da pasta da execução: ${limpar(arquivo)}` : null;
41
+ }
42
+
43
+ /**
44
+ * Onde fica a crew, ou o erro de uso. A crew é uma pasta direta de `crews/` (`crews/<nome>`
45
+ * também vale).
46
+ * @returns {{ erro: string } | { crew: string }}
47
+ */
48
+ function localizar(raiz, args) {
49
+ const erro = erroDeArgumentos(args);
50
+ if (erro) return { erro };
51
+ if (!ehPasta(path.join(raiz, '_opencrew'))) return { erro: MSG.semRaiz };
52
+ const base = path.resolve(raiz, 'crews');
53
+ const nome = args.crew.replace(/^crews[\\/]+/, '');
54
+ if (!dentroDoProjeto(base, nome)) return { erro: MSG.foraDoProjeto(limpar(args.crew)) };
55
+ const pasta = path.resolve(base, nome);
56
+ if (path.dirname(pasta) !== base || !ehPasta(pasta)) return { erro: MSG.crewNaoEncontrada(limpar(args.crew)) };
57
+ const crew = path.basename(pasta);
58
+ const doArquivo = args.arquivo ? erroDoArquivo(raiz, crew, args) : null;
59
+ return doArquivo ? { erro: doArquivo } : { crew };
60
+ }
61
+
62
+ /** Regras 2 e 3: o caminho em que o passo grava; a pasta dele é criada. */
63
+ function saida(raiz, crew, { run, arquivo }) {
64
+ const local = naExecucao(arquivo, crew, run);
65
+ if (!local) return ok(normalizar(arquivo));
66
+ const pasta = `${local.grupo}/${proximaVersao(pastasDe(path.resolve(raiz, local.grupo)))}`;
67
+ criarPasta(path.resolve(raiz, pasta));
68
+ return ok(`${pasta}/${local.nome}`);
69
+ }
70
+
71
+ /** Regra 4: a versão mais nova que tem o arquivo; depois, o próprio grupo, sem pasta de versão. */
72
+ function entrada(raiz, crew, { run, arquivo }) {
73
+ const local = naExecucao(arquivo, crew, run);
74
+ const { grupo, nome } = local ?? { grupo: path.posix.dirname(normalizar(arquivo)), nome: path.posix.basename(normalizar(arquivo)) };
75
+ const versoes = local ? daMaisNova(pastasDe(path.resolve(raiz, grupo))) : [];
76
+ const candidatos = [...versoes.map((versao) => `${grupo}/${versao}/${nome}`), local ? `${grupo}/${nome}` : normalizar(arquivo)];
77
+ const achado = candidatos.find((caminho) => temConteudo(path.resolve(raiz, caminho)));
78
+ return achado ? ok(achado) : falta(candidatos.at(-1));
79
+ }
80
+
81
+ /** Regra 5: o arquivo gravado existe, não está vazio e tem o que o contrato do passo pede. */
82
+ function conferir(raiz, { arquivo, secoes, tldr }) {
83
+ const caminho = normalizar(arquivo);
84
+ const alvo = path.resolve(raiz, caminho);
85
+ if (!temConteudo(alvo)) return reprovado(MOTIVO.ausente);
86
+ const pedido = { secoes: secoes === undefined ? null : Number(secoes), tldr };
87
+ const motivo = pedido.secoes !== null || tldr ? motivoDeReprovacao(lerTexto(alvo), pedido) : null;
88
+ return motivo ? reprovado(motivo) : ok(caminho);
89
+ }
90
+
91
+ function responder(raiz, crew, args) {
92
+ if (args.acao === 'saida') return saida(raiz, crew, args);
93
+ if (args.acao === 'entrada') return entrada(raiz, crew, args);
94
+ if (args.acao === 'conferir') return conferir(raiz, args);
95
+ const pasta = `crews/${crew}/output/${args.run}`;
96
+ criarPasta(path.resolve(raiz, pasta));
97
+ return ok(pasta);
98
+ }
99
+
100
+ /**
101
+ * @param {string[]} argv
102
+ * @param {object} [deps] `cwd` (a pasta do projeto) e `escrever`
103
+ * @returns {number} 0 = a linha `CAMINHO:` saiu · 1 = erro de uso, ou falha ao ler ou criar pasta
104
+ * (a linha de uso e o motivo, ou só o erro; sem linha `CAMINHO:`)
105
+ */
106
+ export function main(argv, deps = {}) {
107
+ const { cwd = process.cwd(), escrever = (s) => process.stdout.write(`${s}\n`) } = deps;
108
+ const args = lerArgs(argv);
109
+ const local = localizar(cwd, args);
110
+ if (local.erro) {
111
+ escrever(USO);
112
+ escrever(local.erro);
113
+ return 1;
114
+ }
115
+ try {
116
+ escrever(responder(cwd, local.crew, args));
117
+ return 0;
118
+ } catch (erro) {
119
+ escrever(`Não consegui resolver o caminho: ${limpar(erro?.message ?? erro)}`);
120
+ return 1;
121
+ }
122
+ }
123
+
124
+ if (ehPrincipal(import.meta.url)) process.exitCode = main(process.argv.slice(2));