jarvis-ai-framework 3.2.0 → 5.0.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 (66) hide show
  1. package/agents/engineering/eng.docs-consolidator.agent.md +81 -0
  2. package/agents/engineering/qa/eng.qa.test-planner.md +7 -7
  3. package/agents/product/prod.discovery-interviewer.md +1 -1
  4. package/bin/README.md +14 -14
  5. package/bin/commands/docs-publish.js +61 -93
  6. package/bin/commands/docs-sync.js +31 -63
  7. package/bin/lib/cli/program.js +23 -25
  8. package/bin/lib/docs/fetch-file.js +29 -26
  9. package/bin/lib/docs/publish-file.js +98 -159
  10. package/bin/lib/docs/storage/build-index.js +108 -0
  11. package/bin/lib/docs/storage/filesystem.js +203 -0
  12. package/bin/lib/docs/storage/index.js +75 -0
  13. package/bin/lib/docs/validate-frontmatter.js +18 -4
  14. package/bin/lib/vcs/api.js +0 -125
  15. package/package.json +2 -2
  16. package/rules/AGENTS.md +12 -9
  17. package/rules/engineering/eng.docs-scraping-rules.md +4 -4
  18. package/rules/engineering/eng.integrations-rules.md +3 -2
  19. package/rules/engineering/rpa/eng.rpa-rules.md +1 -1
  20. package/rules/product/prod-rules.md +5 -6
  21. package/rules-on-demand/engineering/eng.pr-rules.md +9 -31
  22. package/rules-on-demand/engineering/eng.pre-pr-rules.md +12 -19
  23. package/rules-on-demand/product/prod.guided-rules.md +104 -0
  24. package/skills/eng-backend-arch-c4/SKILL.md +3 -3
  25. package/skills/eng-global-docs-write/SKILL.md +1 -1
  26. package/skills/eng-qa-planner/SKILL.md +5 -5
  27. package/skills/jarvis-docs-central/README.md +51 -275
  28. package/skills/jarvis-docs-central/SKILL.md +114 -301
  29. package/skills/jarvis-init/SKILL.md +81 -8
  30. package/templates/ENV-template.md +28 -7
  31. package/templates/engineering/ARD-template.md +40 -7
  32. package/templates/engineering/architecture-template.md +1 -0
  33. package/templates/engineering/central-delivery-template.md +65 -0
  34. package/templates/engineering/central-feature-template.md +86 -0
  35. package/templates/product/prod-breakdown-template.md +20 -2
  36. package/templates/product/prod-discovery-template.md +24 -1
  37. package/templates/product/prod-epic-template.md +32 -1
  38. package/templates/product/prod-frd-template.md +29 -2
  39. package/templates/product/prod-issue-template.md +36 -1
  40. package/templates/product/prod-prd-template.md +33 -2
  41. package/workflows/engineering/eng.build-tech-spec.md +4 -4
  42. package/workflows/engineering/eng.create-ard-from-code.md +15 -41
  43. package/workflows/engineering/eng.create-ard.md +22 -44
  44. package/workflows/engineering/eng.create-rfc.md +10 -36
  45. package/workflows/engineering/eng.pr.md +35 -66
  46. package/workflows/engineering/eng.pre-pr.md +52 -89
  47. package/workflows/engineering/eng.rpa.robot.md +9 -7
  48. package/workflows/engineering/eng.spec-consolidate.md +140 -0
  49. package/workflows/engineering/eng.start.md +23 -22
  50. package/workflows/engineering/eng.work.md +1 -1
  51. package/workflows/engineering/qa/eng.qa-refinement-entry.md +1 -1
  52. package/workflows/engineering/qa/eng.qa-release-signoff.md +3 -3
  53. package/workflows/engineering/qa/eng.qa-sprint-planning.md +2 -2
  54. package/workflows/product/prod.roadmap.preview.md +4 -1
  55. package/workflows/product/prod.spec.ard.md +9 -5
  56. package/workflows/product/prod.spec.breakdown.md +8 -4
  57. package/workflows/product/prod.spec.clarify.md +5 -1
  58. package/workflows/product/prod.spec.discovery.md +7 -3
  59. package/workflows/product/prod.spec.epic.md +12 -40
  60. package/workflows/product/prod.spec.frd.md +13 -39
  61. package/workflows/product/prod.spec.issue.md +5 -1
  62. package/workflows/product/prod.spec.md +3 -1
  63. package/workflows/product/prod.spec.prd.md +13 -40
  64. package/workflows/warm-up.md +10 -12
  65. package/bin/lib/docs/redis-cache.js +0 -49
  66. package/bin/lib/vcs/fetch-raw.js +0 -42
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: eng.docs-consolidator.agent
3
+ description: >
4
+ Especialista em organizar documentação para dois públicos, humanos e IAs. Consolida o que uma
5
+ entrega de fato implementou (documentos de trabalho, architecture.md, plan.md, diff e commits) em
6
+ documentos completos e autossuficientes para o Central docs: um por funcionalidade (estado atual)
7
+ e um registro por entrega. Escreve só na sessão; quem publica é o workflow, depois da confirmação.
8
+ Trigger: /eng.spec-consolidate (inclusive --inicial, chamado pelo /jarvis-init).
9
+ tools: Read, Glob, Grep, Bash, Write, Edit
10
+ ---
11
+
12
+ # Consolidador de documentação (eng.docs-consolidator.agent)
13
+
14
+ ## Papel
15
+
16
+ Você transforma o que foi **implementado** num registro que dure. Duas pessoas vão ler o que você escreve:
17
+ - **humanos:** outro squad, alguém novo no time, a própria pessoa daqui a seis meses;
18
+ - **IAs:** o `/warm-up`, o `/eng.start` e os `/prod.spec.*` consultam o Central docs como verdade sobre o sistema.
19
+
20
+ Por isso o texto tem de ser **completo, autossuficiente e sem ambiguidade**.
21
+
22
+ ## Princípios
23
+
24
+ 1. **Tudo, exatamente tudo.** Leia todas as fontes (abaixo) antes de escrever. Nada que esteja no PRD, no FRD, no ARD, no RFC, no `architecture.md` ou nos commits e que tenha sido implementado pode ficar de fora.
25
+ 2. **Autossuficiente.** Se os documentos de origem se perderem, o seu documento basta. Os originais são citados só como referência (basename), nunca como "ver o PRD para detalhes".
26
+ 3. **O implementado, não o pretendido.** Descreva o que o código faz. O que foi especificado e não entrou vai para "O que mudou em relação ao especificado" ou "Fora do escopo", com o motivo.
27
+ 4. **Para humanos e IAs.**
28
+ - Frases completas e termos definidos na primeira vez.
29
+ - Listas "Quando X, o sistema Y".
30
+ - Tabelas para dados estruturados.
31
+ - Nada implícito, nada de "como combinado".
32
+ 5. **Bons nomes.**
33
+ - Sugira o slug da funcionalidade em kebab-case, curto e pelo nome que o negócio usa (ex.: `feature-checkout-pix`, não `feature-task-123`).
34
+ - Os títulos dizem o que a funcionalidade é.
35
+ 6. **Referências sempre com URL:**
36
+ - card pelo `TASK_MANAGER` do `ENV.md`, com a URL no padrão da `eng.integrations-rules.md` (Jira `/browse/`, Linear `/issue/`, GitHub `/issues/`, Asana); no modo freelance, o número de controle, sem URL;
37
+ - PR/MR pelo `VERSION_CONTROL` ou pelo `remote origin` (número + URL).
38
+ 7. **Nunca inventar.** Sem fonte, pergunte ao agente principal (que pergunta ao usuário) ou registre como "não informado".
39
+ 8. **Sem dados sensíveis.** Nunca e-mails de autor (só o nome), tokens, senhas, valores de variáveis de ambiente nem dados pessoais de clientes. Se o diff contiver algo parecido com um segredo, avise o agente principal.
40
+
41
+ ## Fontes (nesta ordem)
42
+
43
+ 1. `architecture.md` da sessão: `Branch base:`, contexto, decisões, Questões em Aberto respondidas.
44
+ 2. `plan.md` da sessão: fases e comentários (mudanças de direção).
45
+ 3. Documentos de trabalho em `$PROD_DOCS` e `$ENG_DOCS` ligados à tarefa (PRD, FRD, épicos, histórias, ARD, RFC, swagger, QA).
46
+ 4. `git log {base}..HEAD --format='%h|%an|%ad|%s' --date=short` e `git diff --stat {base}...HEAD`.
47
+ 5. O código alterado, só leitura, para confirmar o comportamento real.
48
+ 6. O documento atual da funcionalidade no Central docs (`jarvis docs fetch`), quando for atualização.
49
+ 7. O `gate-checklist.md` e o `pull-request.md` da sessão, se existirem.
50
+
51
+ ## O que você produz
52
+
53
+ Na pasta da sessão, em `consolidation/`:
54
+ - **`feature-{slug}.md`**, no template `$IDE/templates/engineering/central-feature-template.md`. Na atualização, parta do documento atual do Central docs: reescreva as seções afetadas para o estado atual (não acumule "antes/depois") e acrescente a linha no Histórico de entregas.
55
+ - **`delivery-{AAAA-MM-DD}-{task_key}.md`**, no template `$IDE/templates/engineering/central-delivery-template.md`. No modo `--inicial` não há entrega: só o `feature-*` com a linha "Consolidação inicial a partir de {documentos}" no histórico.
56
+
57
+ Todas as seções obrigatórias do template, preenchidas ou com "Nenhum(a)" e o motivo.
58
+
59
+ ## Limites
60
+
61
+ - **Escreve só em `$SESSIONS_DIR/eng/{KEY}/consolidation/`.** Não publica, não comita, não altera o código nem os documentos de trabalho.
62
+ - **No GitHub/GitLab, só consulta** (ex.: `gh pr view`, `gh issue view`) para obter números, URLs e títulos.
63
+ - **Português do Brasil;** nomes técnicos podem ficar em inglês.
64
+
65
+ ## Skills Disponíveis
66
+
67
+ - **jarvis-docs-central**: contrato do Central docs (estrutura, índice, frontmatter) e leitura com `jarvis docs fetch`
68
+
69
+ ## Workflows e Agentes Relacionados
70
+
71
+ - **eng.spec-consolidate**: abre este agente para escrever a consolidação (normal e `--inicial`)
72
+ - **jarvis-init**: oferece a carga inicial, que chama o `eng.spec-consolidate --inicial`
73
+
74
+ ## Checklist antes de devolver
75
+
76
+ - [ ] Todas as fontes lidas (listar quais)
77
+ - [ ] As 11 seções da funcionalidade e as 7 da entrega preenchidas
78
+ - [ ] Card e PR/MR com URL (ou o número de controle no freelance)
79
+ - [ ] Commits com autor (nome, sem e-mail) e data
80
+ - [ ] Nada de segredos, e-mails ou valores de variáveis
81
+ - [ ] Nenhuma frase depende de um documento de origem para ser entendida
@@ -17,12 +17,12 @@ Antes de qualquer ação, determine qual modo aplicar:
17
17
  Aplicar quando invocado via `eng.qa-refinement-entry` ou quando o argumento recebido for um ID de card ou caminho de spec e não houver código implementado na branch ainda.
18
18
  - Fonte de dados: card no $TASK_MANAGER via integração disponível (Jira MCP)
19
19
  - Não executar `git diff`, `git log` ou qualquer comando que dependa de código
20
- - Saída: `$DOCS_FOLDER/engineering/qa/strategies/{task-id}-test-strategy.md`
20
+ - Saída: `$ENG_DOCS/qa/strategies/{task-id}-test-strategy.md`
21
21
 
22
22
  **Modo B — Análise de Cobertura (pós-código)**
23
23
  Aplicar quando invocado via `eng-qa`, `eng-qa-planner` ou quando houver código implementado na branch (git diff retorna resultados).
24
24
  - Fonte de dados: `git diff origin/main...HEAD`
25
- - Saída: `$DOCS_FOLDER/engineering/qa/{task-id}-test_coverage_branch_report.md`
25
+ - Saída: `$ENG_DOCS/qa/{task-id}-test_coverage_branch_report.md`
26
26
 
27
27
  Regra de decisão: se `git diff origin/main...HEAD` retornar vazio, operar em Modo A. Se retornar código, operar em Modo B.
28
28
 
@@ -225,7 +225,7 @@ Antes de finalizar cada cenário, aplicar este filtro:
225
225
 
226
226
  ### A.7. Salvar o Documento
227
227
 
228
- Salvar em: `$DOCS_FOLDER/engineering/qa/strategies/{task-id}-test-strategy.md`
228
+ Salvar em: `$ENG_DOCS/qa/strategies/{task-id}-test-strategy.md`
229
229
 
230
230
  Estrutura obrigatória:
231
231
  ```
@@ -353,7 +353,7 @@ Isso permite:
353
353
 
354
354
  ### B.6. Gerar Relatório de Cobertura de Testes
355
355
 
356
- Crie um arquivo **`$DOCS_FOLDER/engineering/qa/{task-id}-test_coverage_branch_report.md`** contendo:
356
+ Crie um arquivo **`$ENG_DOCS/qa/{task-id}-test_coverage_branch_report.md`** contendo:
357
357
 
358
358
  ```markdown
359
359
  # Análise de Cobertura de Testes da Branch
@@ -456,9 +456,9 @@ Esforço estimado: [estimativa aproximada]
456
456
 
457
457
  ## Saída
458
458
 
459
- **Modo A**: `$DOCS_FOLDER/engineering/qa/strategies/{task-id}-test-strategy.md` (gerado em A.7)
459
+ **Modo A**: `$ENG_DOCS/qa/strategies/{task-id}-test-strategy.md` (gerado em A.7)
460
460
 
461
- **Modo B**: `$DOCS_FOLDER/engineering/qa/{task-id}-test_coverage_branch_report.md`
461
+ **Modo B**: `$ENG_DOCS/qa/{task-id}-test_coverage_branch_report.md`
462
462
 
463
463
  As recomendações devem ser **específicas, acionáveis** e, quando possível, incluir **exemplos de estrutura de teste**.
464
464
  Foque **exclusivamente** no que foi alterado na branch atual para manter o escopo controlado.
@@ -470,7 +470,7 @@ Foque **exclusivamente** no que foi alterado na branch atual para manter o escop
470
470
  Após gerar o relatório de cobertura, sugira ao usuário invocar o agente **eng.qa.testing-engineer** para escrever os testes identificados como ausentes:
471
471
 
472
472
  ```
473
- @eng.qa.testing-engineer Escreva os testes identificados no relatório $DOCS_FOLDER/engineering/qa/{task-id}-test_coverage_branch_report.md
473
+ @eng.qa.testing-engineer Escreva os testes identificados no relatório $ENG_DOCS/qa/{task-id}-test_coverage_branch_report.md
474
474
  ```
475
475
 
476
476
  Fluxo completo recomendado:
@@ -75,7 +75,7 @@ Não use quando já existe uma spec (use `/prod.spec.clarify`) ou quando o escop
75
75
  As skills são apoio, nunca a fonte da decisão. A busca em `$PROD_DOCS` não é uma skill: é feita sempre, antes de perguntar.
76
76
 
77
77
  ### jarvis-docs-central
78
- Para buscar documentação do produto no repositório central, quando `CENTRAL_DOCS_REPO` estiver configurado:
78
+ Para conhecer o que já foi implementado no produto (funcionalidades do Central docs), quando `CENTRAL_DOCS_STORAGE` estiver configurado:
79
79
  - Arquivo: `$IDE/skills/jarvis-docs-central/SKILL.md`
80
80
  - Confiança: sempre
81
81
 
package/bin/README.md CHANGED
@@ -17,17 +17,17 @@ bin/
17
17
  │ ├── program.js # Único lugar que monta o commander — ver "Arquitetura"
18
18
  │ └── shared-options.js # .option() repetidos entre comandos (--ide, --quiet, etc.)
19
19
  ├── vcs/ # Adapters de Git host (GitLab/GitHub/Bitbucket)
20
- │ ├── api.js # Lógica HTTP real (createIssue, createMergeRequest, fetchRawFile)
21
- │ ├── create-merge.js, create-issue.js, fetch-raw.js # função exportada + CLI direto (ver abaixo)
20
+ │ ├── api.js # Lógica HTTP real (createIssue, createMergeRequest)
21
+ │ ├── create-merge.js, create-issue.js # função exportada + CLI direto (ver abaixo)
22
22
  ├── tasks/
23
23
  │ └── comment.js # Adapter de task manager (jira/linear/github/asana) — mesmo padrão
24
24
  ├── config/ # ide-config.js (fonte de verdade de IDEs), constants.js
25
25
  ├── core/ # scanner.js, sync-engine.js, profile-filter.js, token-report.js
26
- ├── docs/ # central-docs: fetch/publish + validação de frontmatter
27
- │ ├── validate-frontmatter.js # Valida/extrai frontmatter de PRD/FRD/ARD/RFC
28
- │ ├── redis-cache.js # Cache best-effort via `redis-cli` (get/setex/del)
29
- │ ├── fetch-file.js # fetchFile() — usado por `docs sync`/`docs fetch` e por publish-file.js
30
- │ └── publish-file.js # publishFile() — usado por `docs publish`
26
+ ├── docs/ # Central docs: fetch/publish pelo adaptador + validação de frontmatter
27
+ │ ├── validate-frontmatter.js # Valida/extrai frontmatter (PRD, FRD, épico, história, tarefa, ARD, RFC)
28
+ │ ├── fetch-file.js # openStorageFromEnv() + fetchFile() — `docs sync`/`docs fetch`/`docs publish`
29
+ │ ├── publish-file.js # tipos, resolveDestPath() e publishFile() — `docs publish`
30
+ │ └── storage/ # adaptadores (CENTRAL_DOCS_STORAGE): index.js (fábrica), filesystem.js (pasta + git), build-index.js
31
31
  ├── utils/ # logger.js, ui.js, paths.js, frontmatter.js, git-parser.js, npmrc-parser.js
32
32
  ├── flow-map/ # `jarvis map` — grafo de chamadas entre workflows/skills/agents
33
33
  ├── auth/ # session.js (auth.json legado, hoje só `jarvis logout` o lê)
@@ -77,22 +77,22 @@ O parser manual antigo nunca validava nada: qualquer `--flag` era aceito e ignor
77
77
 
78
78
  ## Argumentos: `[opcional]`, não `<obrigatório>` — de propósito
79
79
 
80
- Note que `info`, `vcs fetch-raw` e `tasks comment` declaram seus positionals como **opcionais** (`[target]`, `[file-path]`) mesmo sendo, na prática, obrigatórios para o comando funcionar. Isso é deliberado: se o commander validar "argumento obrigatório ausente" sozinho, ele intercepta **antes** da função rodar — e cada uma dessas funções já tem sua própria validação com mensagem de uso customizada e mais específica (`info.js`: `"❌ Uso incorreto\nUso: jarvis info <type/name>\nExemplo: ..."`). Deixar o positional opcional no commander e a validação dentro da função preserva essa mensagem.
80
+ Note que `info` e `tasks comment` declaram seus positionals como **opcionais** (`[target]`, `[card-key]`) mesmo sendo, na prática, obrigatórios para o comando funcionar. Isso é deliberado: se o commander validar "argumento obrigatório ausente" sozinho, ele intercepta **antes** da função rodar — e cada uma dessas funções já tem sua própria validação com mensagem de uso customizada e mais específica (`info.js`: `"❌ Uso incorreto\nUso: jarvis info <type/name>\nExemplo: ..."`). Deixar o positional opcional no commander e a validação dentro da função preserva essa mensagem.
81
81
 
82
- **A mesma regra vale para `--flag` que já tem validação própria.** Nenhum comando aqui usa `.requiredOption()` ou `.choices()` do commander, mesmo quando faria sentido à primeira vista (ex: `--tipo` em `docs publish` só aceita `prd|frd|ard|rfc|swagger|qa-report`). `docsPublish.js` já valida isso e mostra um bloco de exemplos de uso bem mais rico que o que o commander mostraria. Usar `.choices()` ali faria o commander interceptar o valor errado com uma mensagem genérica, perdendo esse texto. **Ao adicionar uma opção nova**: se a função já vai validar o valor por dentro, deixe o commander permissivo (`.option()` simples) e confie na validação interna. Se não há validação nenhuma hoje, `.choices()`/`.argParser()` são bons lugares para adicionar uma — não há essa perda a proteger.
82
+ **A mesma regra vale para `--flag` que já tem validação própria.** Nenhum comando aqui usa `.requiredOption()` ou `.choices()` do commander, mesmo quando faria sentido à primeira vista (ex: `--tipo` em `docs publish` só aceita os tipos de `VALID_TYPES`). `docsPublish.js` já valida isso e mostra um bloco de exemplos de uso bem mais rico que o que o commander mostraria. Usar `.choices()` ali faria o commander interceptar o valor errado com uma mensagem genérica, perdendo esse texto. **Ao adicionar uma opção nova**: se a função já vai validar o valor por dentro, deixe o commander permissivo (`.option()` simples) e confie na validação interna. Se não há validação nenhuma hoje, `.choices()`/`.argParser()` são bons lugares para adicionar uma — não há essa perda a proteger.
83
83
 
84
84
  ## `program.command()` vs `program.addCommand()`
85
85
 
86
86
  Este código usa `.command(nome)` (que cria e já anexa o subcomando) em todo lugar — não `.addCommand(instanciaDeCommand)`. São equivalentes; `.addCommand()` só compensa quando o `Command` é montado em outro módulo e precisa ser importado antes de anexar. Como tudo mora em `program.js`, não há motivo para o nível extra de indireção.
87
87
 
88
- ## Os 4 "scripts standalone" (`vcs/*.js`, `tasks/comment.js`): dupla forma
88
+ ## Os 3 "scripts standalone" (`vcs/*.js`, `tasks/comment.js`): dupla forma
89
89
 
90
- `create-merge.js`, `create-issue.js`, `fetch-raw.js` e `comment.js` são chamados de duas formas:
90
+ `create-merge.js`, `create-issue.js` e `comment.js` são chamados de duas formas:
91
91
 
92
92
  1. **Como subcomando do CLI**: `jarvis vcs create-merge --source ... --target ...` (via `program.js`)
93
93
  2. **Direto, por skills/workflows que ainda shell-am pra eles**: `node bin/lib/vcs/create-merge.js --source ...`
94
94
 
95
- Cada um dos 4 arquivos segue o mesmo padrão: exporta uma função (`runCreateMerge`, `runCreateIssue`, `runFetchRaw`, `runComment`) com a lógica, e termina com um guard que só lê `process.argv` quando o arquivo é executado diretamente:
95
+ Cada um dos 3 arquivos segue o mesmo padrão: exporta uma função (`runCreateMerge`, `runCreateIssue`, `runComment`) com a lógica, e termina com um guard que só lê `process.argv` quando o arquivo é executado diretamente:
96
96
 
97
97
  ```js
98
98
  // Compatibilidade: continua funcionando como `node bin/lib/vcs/create-merge.js --source ...`
@@ -131,9 +131,9 @@ Os 3 arquivos mais críticos sem cobertura até pouco tempo atrás (`env-loader.
131
131
  - **`env-loader.js`** (e `flow-map.test.js`, mais antigo): `mkdtempSync(join(tmpdir(), "jarvis-..."))` monta um workspace fake com `.{ide}/ENV.md`, roda a função contra ele, `rmSync` no `finally`.
132
132
  - **`sync-engine.js`**: `getFrameworkRoot()` (`utils/paths.js`) já tem um escape hatch — `process.env.JARVIS_ROOT`, se definido e existir, vence sobre o framework real. O teste aponta `JARVIS_ROOT` para um fixture mínimo (`agents/`, `skills/`, etc. com 1 arquivo cada) em vez de sincronizar o framework inteiro.
133
133
  - **`vcs/api.js`**: usa `mock.method(globalThis, "fetch", ...)` do `node:test` pra controlar a resposta por vendor, sem mockar o módulo nem tocar rede real. **Atenção**: toda função de rede chama `getVcsToken()` antes do fetch — fixe `GITLAB_TOKEN`/`GITHUB_TOKEN`/`BITBUCKET_TOKEN` via env var no teste (ver `withEnv` em `vcs-api.test.js`), senão o resultado depende do que a máquina que roda o teste tiver em `.npmrc`/`gh auth`.
134
- - **`docs/fetch-file.js`/`docs/publish-file.js`**: mesmo mock de `fetch`. `redis-cli` (chamado por `docs/redis-cache.js`) **não dá pra mockar** — é um módulo nativo (`node:child_process`) não configurável, `mock.method` lança `TypeError: Cannot redefine property`. Como pode haver um `redis-cli` de verdade instalado (com ou sem servidor acessível) na máquina que roda o teste, cada teste usa uma chave de cache única (`docs:${randomUUID()}...`) em vez de mockar o cache — garante cache-miss determinístico sem depender do estado de nenhum Redis real.
134
+ - **`docs/` (Central docs)**: tudo em pasta temporária (`mkdtempSync`). A camada git do adaptador `filesystem` usa um repositório `git init --bare` local como `origin` (sem rede), e o `createMergeRequest` é injetado pelo parâmetro `deps` de `getStorage` (ver `docs-storage.test.js`).
135
135
 
136
- **Armadilha com `async`**: o helper `withEnv(vars, fn)` (repetido nos 3 arquivos acima) precisa de `return **await** fn()`, não `return fn()`, dentro do `try`. Sem o `await`, o `finally` que restaura a env var roda assim que `fn()` retorna uma Promise pendente — ou seja, **antes** da função terminar. Isso só não quebra quando a leitura da env var acontece antes do primeiro `await` de `fn` (ex: `fetchRawFile` lê o token antes do seu próprio `fetch`); quebra silenciosamente em qualquer função com mais de um `await` no meio do caminho (ex: `publishFile`, que busca o `index.md` antes de chegar no commit, que é onde o token é lido).
136
+ **Armadilha com `async`**: o helper `withEnv(vars, fn)` (em `vcs-api.test.js`) precisa de `return **await** fn()`, não `return fn()`, dentro do `try`. Sem o `await`, o `finally` que restaura a env var roda assim que `fn()` retorna uma Promise pendente — ou seja, **antes** da função terminar. Isso só não quebra quando a leitura da env var acontece antes do primeiro `await` de `fn` (ex: `createIssue` lê o token antes do seu próprio `fetch`); quebra silenciosamente em qualquer função com mais de um `await` antes de ler a variável.
137
137
 
138
138
  Ao escrever um teste novo para um arquivo que toca `fs`/rede, prefira um desses padrões a mockar o módulo inteiro ou refatorar pra injeção de dependência — nenhum dos arquivos acima precisou mudar uma linha de lógica de produção para ganhar teste.
139
139
 
@@ -1,148 +1,116 @@
1
- import { loadEnv, resolveEnvPath } from "../lib/env-loader.js";
2
1
  import { existsSync } from "node:fs";
3
- import { publishFile } from "../lib/docs/publish-file.js";
2
+ import { openStorageFromEnv } from "../lib/docs/fetch-file.js";
3
+ import { publishFile, VALID_TYPES, WORKING_DOC_TYPES } from "../lib/docs/publish-file.js";
4
4
  import { logger, configureFromFlags } from "../lib/utils/logger.js";
5
5
 
6
6
  /**
7
- * Publica documento no central-docs via GitLab API (branch + MR)
7
+ * Publica um documento no Central docs pelo adaptador configurado.
8
+ * Sem `.git` na pasta: copia e regenera o índice. Com `.git`: branch, commit, push e PR.
8
9
  *
9
10
  * @param {Object} flags - Flags do comando
10
11
  * @param {string} flags.file - Caminho do arquivo local
11
- * @param {string} flags.tipo - Tipo do documento (prd|frd|ard|rfc)
12
- * @param {string} flags.feature - Slug da feature (kebab-case)
13
- * @param {string} flags.squad - Squad (opcional, override do ENV.md)
14
- * @param {string} flags.workspace - Workspace (opcional, override do ENV.md)
12
+ * @param {string} flags.tipo - feature | delivery (ver VALID_TYPES)
13
+ * @param {string} [flags.produto] - Pasta do produto (padrão: WORKSPACE do ENV.md)
14
+ * @param {string} [flags.feature] - Slug, só se o nome do arquivo fugir do padrão {tipo}-{nome}.md
15
+ * @param {string} [flags.squad] - Override do SQUAD do ENV.md
16
+ * @param {string} [flags.workspace] - Override do WORKSPACE (padrão do --produto)
15
17
  * @param {string} flags.envFile - Path customizado do ENV.md
16
18
  */
17
19
  export async function docsPublish(flags = {}) {
18
20
  configureFromFlags(flags);
19
21
 
20
- const { file, tipo, feature, squad: squadOverride, workspace: workspaceOverride } = flags;
22
+ const {
23
+ file,
24
+ tipo,
25
+ feature,
26
+ produto,
27
+ squad: squadOverride,
28
+ workspace: workspaceOverride,
29
+ } = flags;
21
30
 
22
- // Validar parâmetros obrigatórios
23
- if (!file || !tipo || !feature) {
31
+ if (!file || !tipo) {
24
32
  logger.error("❌ Parâmetros obrigatórios faltando\n");
25
33
  logger.error(
26
- "Uso: jarvis docs publish --file <path> --tipo <prd|frd|ard|rfc|swagger|qa-report> --feature <slug> [--squad <squad>] [--workspace <workspace>]\n"
34
+ `Uso: jarvis docs publish --file <path> --tipo <${VALID_TYPES.join("|")}> [--produto <produto>] [--feature <slug>] [--squad <squad>]\n`
35
+ );
36
+ logger.error(
37
+ "Normalmente chamado pelo /eng.spec-consolidate, que gera os dois arquivos na sessão:"
38
+ );
39
+ logger.error(
40
+ " jarvis docs publish --file .jarvis/sessions/eng/task-123/consolidation/delivery-2026-10-15-task-123.md --tipo delivery"
41
+ );
42
+ logger.error(
43
+ " jarvis docs publish --file .jarvis/sessions/eng/task-123/consolidation/feature-checkout.md --tipo feature"
27
44
  );
28
- logger.error("Exemplo:");
29
- logger.error(" jarvis docs publish \\");
30
- logger.error(" --file ./docs/engineering/ard-api-wallet.md \\");
31
- logger.error(" --tipo ard \\");
32
- logger.error(" --feature api-wallet-auth-jwt");
33
- logger.error("");
34
- logger.error(" # Publicar Swagger/OpenAPI:");
35
- logger.error(" jarvis docs publish \\");
36
- logger.error(" --file ./docs/engineering/swagger/api-wallet.yaml \\");
37
- logger.error(" --tipo swagger \\");
38
- logger.error(" --feature api-wallet");
39
- logger.error("");
40
- logger.error(" # Com override de squad/workspace:");
41
- logger.error(" jarvis docs publish \\");
42
- logger.error(" --file ./docs/engineering/ard-api-wallet.md \\");
43
- logger.error(" --tipo ard \\");
44
- logger.error(" --feature api-wallet-auth-jwt \\");
45
- logger.error(" --squad core \\");
46
- logger.error(" --workspace meu-workspace");
47
45
  process.exit(1);
48
46
  }
49
47
 
50
- // Validar tipo
51
- const validTypes = ["prd", "frd", "ard", "rfc", "swagger", "qa-report"];
52
- if (!validTypes.includes(tipo)) {
53
- logger.error(`❌ Tipo inválido: ${tipo}`);
54
- logger.error(`Tipos válidos: ${validTypes.join(", ")}`);
48
+ if (WORKING_DOC_TYPES.includes(tipo)) {
49
+ logger.error(
50
+ `❌ O Central docs guarda só o que foi implementado: "${tipo}" é um documento de trabalho.`
51
+ );
52
+ logger.error(" Ele fica em docs/jarvis/. Gere a consolidação com /eng.spec-consolidate.");
55
53
  process.exit(1);
56
54
  }
57
55
 
58
- // Validar se arquivo existe
59
- if (!existsSync(file)) {
60
- logger.error(`❌ Arquivo não encontrado: ${file}`);
56
+ if (!VALID_TYPES.includes(tipo)) {
57
+ logger.error(`❌ Tipo inválido: ${tipo}`);
58
+ logger.error(`Tipos válidos: ${VALID_TYPES.join(", ")}`);
61
59
  process.exit(1);
62
60
  }
63
61
 
64
- const cwd = process.cwd();
65
-
66
- // Resolver path do ENV.md (suporta --ide e --env-file)
67
- let envPath;
68
- try {
69
- const resolved = resolveEnvPath(cwd, flags);
70
- envPath = resolved.path;
71
- } catch (error) {
72
- logger.error("❌ Erro ao resolver ENV.md:", error.message);
62
+ if (!existsSync(file)) {
63
+ logger.error(`❌ Arquivo não encontrado: ${file}`);
73
64
  process.exit(1);
74
65
  }
75
66
 
76
- // Carregar ENV.md
77
67
  let env;
68
+ let storage;
78
69
  try {
79
- env = loadEnv(cwd, envPath);
70
+ ({ env, storage } = openStorageFromEnv(flags));
80
71
  } catch (error) {
81
- logger.error("❌ Erro ao carregar ENV.md:", error.message);
72
+ logger.error("❌ Central docs:", error.message);
82
73
  process.exit(1);
83
74
  }
84
75
 
85
- // Verificar se central-docs está configurado
86
- if (!env.CENTRAL_DOCS_REPO) {
87
- logger.error("❌ CENTRAL_DOCS_REPO não configurado no ENV.md");
88
- logger.error("\nConfigure a URL do repositório central:");
89
- logger.error(" CENTRAL_DOCS_REPO=https://gitlab.com/org/central-docs.git");
76
+ if (!storage) {
77
+ logger.error("❌ Central docs desligado (CENTRAL_DOCS_STORAGE vazio no ENV.md).");
78
+ logger.error(" Configure-o com o /jarvis-init.");
90
79
  process.exit(1);
91
80
  }
92
81
 
93
- // Usar override de squad/workspace se fornecido, senão usar do ENV.md
94
82
  const squad = squadOverride || env.SQUAD;
95
- const workspace = workspaceOverride || env.WORKSPACE;
83
+ const product = produto || workspaceOverride || env.WORKSPACE;
96
84
 
97
- if (!squad || !workspace) {
85
+ if (!squad || !product) {
98
86
  logger.error("❌ SQUAD ou WORKSPACE não definidos");
99
- logger.error("\nOpções:");
100
- logger.error(" 1. Definir no ENV.md:");
101
- logger.error(" SQUAD=core");
102
- logger.error(" WORKSPACE=meu-workspace");
103
- logger.error("");
104
- logger.error(" 2. Passar via flags:");
105
- logger.error(" --squad core --workspace meu-workspace");
87
+ logger.error(" Defina no ENV.md ou passe --squad <squad> --produto <produto>");
106
88
  process.exit(1);
107
89
  }
108
90
 
109
- logger.info(`📤 Publicando ${tipo.toUpperCase()} ${feature}...\n`);
91
+ logger.info(`📤 Publicando ${tipo.toUpperCase()} (${file})...\n`);
110
92
 
111
93
  try {
112
- await publishFile({
113
- localFile: file,
114
- tipo,
115
- feature,
116
- squad,
117
- workspace,
118
- repo: env.CENTRAL_DOCS_REPO,
119
- targetBranch: env.CENTRAL_DOCS_TARGET_BRANCH || "dev",
120
- cacheTtl: env.CENTRAL_DOCS_CACHE_TTL || "3600",
121
- });
94
+ await publishFile({ storage, localFile: file, tipo, squad, produto: product, feature });
95
+ logger.info("");
96
+ logger.info("✅ Publicação concluída!");
122
97
  } catch (error) {
123
98
  logger.error("\n❌ Erro ao publicar documento\n");
124
99
  logger.error(error.message);
125
100
 
126
- // Dicas de troubleshooting
127
- logger.error("\n💡 Troubleshooting:\n");
128
-
129
- if (error.message.includes("Token GitLab")) {
130
- logger.error("1. Configure o token GitLab no .npmrc:");
131
- logger.error(" //gitlab.com/api/v4/packages/npm/:_authToken=seu-token\n");
132
- }
133
-
134
101
  if (error.message.includes("Campos obrigatórios")) {
135
- logger.error("2. Verifique o frontmatter do documento:");
136
- logger.error(` Tipo ${tipo} requer campos específicos no YAML\n`);
102
+ logger.error(
103
+ `\n💡 Verifique o frontmatter do documento: o tipo ${tipo} exige campos específicos.`
104
+ );
137
105
  }
138
-
139
- if (error.message.includes("401") || error.message.includes("403")) {
140
- logger.error("4. Verifique permissões no GitLab:");
141
- logger.error(" Token precisa de scopes: api, read_api, write_repository\n");
106
+ if (storage.isGit) {
107
+ logger.error(
108
+ "\n💡 Central docs com git: confira se a pasta está limpa (git status), se você tem permissão de push"
109
+ );
110
+ logger.error(
111
+ " e se o token do vendor está configurado (GitLab .npmrc, GitHub gh auth, BITBUCKET_TOKEN)."
112
+ );
142
113
  }
143
-
144
- logger.error("Para mais detalhes, execute com --verbose");
145
-
146
114
  process.exit(1);
147
115
  }
148
116
  }
@@ -1,10 +1,12 @@
1
- import { loadEnv, resolveEnvPath } from "../lib/env-loader.js";
2
- import { fetchFile } from "../lib/docs/fetch-file.js";
1
+ import { openStorageFromEnv } from "../lib/docs/fetch-file.js";
3
2
  import { logger, configureFromFlags } from "../lib/utils/logger.js";
4
3
 
4
+ const COUNTED_TYPES = ["feature", "delivery"];
5
+ const LABELS = { feature: "funcionalidade(s)", delivery: "entrega(s) recente(s)" };
6
+
5
7
  /**
6
- * Sincroniza documentação do central-docs
7
- * Executado automaticamente em /warm-up
8
+ * Sincroniza a documentação do Central docs (lê `{SQUAD}/index.md` pelo adaptador).
9
+ * Executado automaticamente em /warm-up.
8
10
  *
9
11
  * @param {Object} flags - Flags do comando
10
12
  * @param {boolean} flags.silent - Modo silencioso (nem erros aparecem)
@@ -15,87 +17,53 @@ import { logger, configureFromFlags } from "../lib/utils/logger.js";
15
17
  export async function docsSync(flags = {}) {
16
18
  configureFromFlags(flags);
17
19
 
18
- const cwd = process.cwd();
19
-
20
- // Resolver path do ENV.md (suporta --ide e --env-file)
21
- let envPath;
22
- try {
23
- const resolved = resolveEnvPath(cwd, flags);
24
- envPath = resolved.path;
25
- } catch (error) {
26
- logger.error("❌ Erro ao resolver ENV.md:", error.message);
27
- process.exit(1);
28
- }
29
-
30
- // Carregar ENV.md
31
20
  let env;
21
+ let storage;
32
22
  try {
33
- env = loadEnv(cwd, envPath);
23
+ ({ env, storage } = openStorageFromEnv(flags));
34
24
  } catch (error) {
35
- logger.error("❌ Erro ao carregar ENV.md:", error.message);
25
+ logger.error("❌ Central docs:", error.message);
36
26
  process.exit(1);
37
27
  }
38
28
 
39
- // Verificar se central-docs está configurado
40
- if (!env.CENTRAL_DOCS_REPO) {
41
- logger.info("ℹ️ Central docs não configurado (CENTRAL_DOCS_REPO vazio)");
29
+ if (!storage) {
30
+ logger.info("ℹ️ Central docs desligado (CENTRAL_DOCS_STORAGE vazio)");
42
31
  return;
43
32
  }
44
33
 
45
- const { SQUAD, WORKSPACE, CENTRAL_DOCS_REF = "main" } = env;
46
-
47
- if (!SQUAD || !WORKSPACE) {
48
- logger.error("❌ SQUAD ou WORKSPACE não definidos no ENV.md");
34
+ const { SQUAD } = env;
35
+ if (!SQUAD) {
36
+ logger.error("❌ SQUAD não definido no ENV.md");
49
37
  process.exit(1);
50
38
  }
51
39
 
52
- logger.info(`🔄 Sincronizando docs do squad ${SQUAD}...`);
53
-
54
- // Buscar index.md do squad
55
- const indexPath = `${SQUAD}/index.md`;
40
+ logger.info(
41
+ `🔄 Sincronizando docs do squad ${SQUAD} (${storage.kind}${storage.isGit ? " + git" : ""})...`
42
+ );
56
43
 
57
44
  try {
58
- const content = await fetchFile(indexPath, CENTRAL_DOCS_REF, {
59
- repo: env.CENTRAL_DOCS_REPO,
60
- cacheTtl: env.CENTRAL_DOCS_CACHE_TTL || "3600",
61
- });
62
-
45
+ const content = await storage.fetch(`${SQUAD}/index.md`);
63
46
  logger.info("✅ Docs sincronizados");
64
47
 
65
- if (content) {
66
- logger.debug("\n📄 Conteúdo do index.md:\n");
67
- logger.debug(content);
68
- }
48
+ logger.debug("\n📄 Conteúdo do index.md:\n");
49
+ logger.debug(content);
69
50
 
70
- // Parsear index.md para contar documentos
71
- if (content) {
72
- const prdCount = (content.match(/\[prd-.*\.md\]/g) || []).length;
73
- const frdCount = (content.match(/\[frd-.*\.md\]/g) || []).length;
74
- const ardCount = (content.match(/\[ard-.*\.md\]/g) || []).length;
75
- const rfcCount = (content.match(/\[rfc-.*\.md\]/g) || []).length;
51
+ const counts = COUNTED_TYPES.map((tipo) => [
52
+ tipo,
53
+ (content.match(new RegExp(`\\[${tipo}-[^\\]]*\\]`, "g")) || []).length,
54
+ ]).filter(([, n]) => n > 0);
55
+ const total = counts.reduce((sum, [, n]) => sum + n, 0);
76
56
 
77
- const total = prdCount + frdCount + ardCount + rfcCount;
78
-
79
- if (total > 0) {
80
- logger.info(`📊 ${total} documentos disponíveis:`);
81
- if (prdCount > 0) logger.info(` - ${prdCount} PRD(s)`);
82
- if (frdCount > 0) logger.info(` - ${frdCount} FRD(s)`);
83
- if (ardCount > 0) logger.info(` - ${ardCount} ARD(s)`);
84
- if (rfcCount > 0) logger.info(` - ${rfcCount} RFC(s)`);
85
- }
57
+ if (total > 0) {
58
+ logger.info(`📊 ${total} documentos disponíveis:`);
59
+ for (const [tipo, n] of counts) logger.info(` - ${n} ${LABELS[tipo]}`);
86
60
  }
87
61
  } catch (error) {
88
62
  logger.error("❌ Erro ao sincronizar docs:", error.message);
89
-
90
- // Dicas de troubleshooting
91
- if (error.message.includes("Token GitLab")) {
92
- logger.error("\n💡 Configure o token GitLab no .npmrc:");
93
- logger.error(" //gitlab.com/api/v4/packages/npm/:_authToken=seu-token");
94
- } else if (error.message.includes("não encontrado")) {
95
- logger.error("\n💡 Verifique se o squad existe no central-docs:");
96
- logger.error(` ${SQUAD}/index.md`);
63
+ if (error.code === 404) {
64
+ logger.error("\n💡 O squad ainda não tem índice no Central docs:");
65
+ logger.error(` ${SQUAD}/index.md (é criado na primeira publicação: jarvis docs publish)`);
97
66
  }
98
-
99
67
  process.exit(1);
100
68
  }
101
69
  }