@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.
- package/CHANGELOG.md +95 -0
- package/README.md +52 -2
- package/package.json +1 -1
- package/templates/AGENTS.md +3 -0
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/core/architect.agent.yaml +1 -1
- package/templates/_opencrew/core/prompts/discovery.prompt.md +1 -1
- package/templates/_opencrew/core/prompts/entrega.prompt.md +131 -0
- package/templates/_opencrew/core/runner.pipeline.md +91 -98
- package/templates/_opencrew/core/scripts/caminho/argumentos.mjs +49 -0
- package/templates/_opencrew/core/scripts/caminho/disco.mjs +36 -0
- package/templates/_opencrew/core/scripts/caminho/nucleo.mjs +61 -0
- package/templates/_opencrew/core/scripts/caminho.mjs +124 -0
- package/templates/_opencrew/core/scripts/entrega/argumentos.mjs +46 -0
- package/templates/_opencrew/core/scripts/entrega/canais.mjs +51 -0
- package/templates/_opencrew/core/scripts/entrega/fora.mjs +49 -0
- package/templates/_opencrew/core/scripts/entrega/gravar.mjs +83 -0
- package/templates/_opencrew/core/scripts/entrega/leiame.mjs +89 -0
- package/templates/_opencrew/core/scripts/entrega/leitor.mjs +120 -0
- package/templates/_opencrew/core/scripts/entrega/longas.mjs +109 -0
- package/templates/_opencrew/core/scripts/entrega/nomes.mjs +78 -0
- package/templates/_opencrew/core/scripts/entrega/passos.mjs +99 -0
- package/templates/_opencrew/core/scripts/entrega/pendencias.mjs +87 -0
- package/templates/_opencrew/core/scripts/entrega/separar.mjs +103 -0
- package/templates/_opencrew/core/scripts/entrega/texto.mjs +95 -0
- package/templates/_opencrew/core/scripts/entregar.mjs +141 -0
- package/templates/_opencrew/core/scripts/verificar/pecas.mjs +4 -4
- package/templates/_opencrew/core/scripts/verificar/secoes.mjs +22 -2
- 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
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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
|
|
509
|
-
|
|
510
|
-
|
|
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**:
|
|
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
|
|
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 —
|
|
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}/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 `
|
|
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
|
-
`
|
|
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 `
|
|
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 `
|
|
608
|
-
4. If second attempt still has ANY `
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
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
|
|
639
|
-
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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.
|
|
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
|
-
📁
|
|
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));
|