jarvis-ai-framework 3.2.0 → 4.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.
- package/agents/product/prod.discovery-interviewer.md +1 -1
- package/bin/README.md +14 -14
- package/bin/commands/docs-publish.js +50 -92
- package/bin/commands/docs-sync.js +30 -63
- package/bin/lib/cli/program.js +23 -25
- package/bin/lib/docs/fetch-file.js +29 -26
- package/bin/lib/docs/publish-file.js +89 -161
- package/bin/lib/docs/storage/build-index.js +87 -0
- package/bin/lib/docs/storage/filesystem.js +203 -0
- package/bin/lib/docs/storage/index.js +75 -0
- package/bin/lib/docs/validate-frontmatter.js +14 -4
- package/bin/lib/vcs/api.js +0 -125
- package/package.json +2 -2
- package/rules/AGENTS.md +12 -9
- package/rules/engineering/eng.integrations-rules.md +2 -1
- package/rules/product/prod-rules.md +4 -5
- package/rules-on-demand/product/prod.guided-rules.md +104 -0
- package/skills/jarvis-docs-central/README.md +57 -269
- package/skills/jarvis-docs-central/SKILL.md +126 -296
- package/skills/jarvis-init/SKILL.md +46 -7
- package/templates/ENV-template.md +20 -5
- package/templates/engineering/ARD-template.md +39 -6
- package/templates/engineering/architecture-template.md +1 -0
- package/templates/product/prod-breakdown-template.md +20 -2
- package/templates/product/prod-discovery-template.md +24 -1
- package/templates/product/prod-epic-template.md +32 -1
- package/templates/product/prod-frd-template.md +29 -2
- package/templates/product/prod-issue-template.md +36 -1
- package/templates/product/prod-prd-template.md +33 -2
- package/workflows/engineering/eng.build-tech-spec.md +2 -2
- package/workflows/engineering/eng.create-ard-from-code.md +9 -11
- package/workflows/engineering/eng.create-ard.md +15 -13
- package/workflows/engineering/eng.create-rfc.md +8 -10
- package/workflows/engineering/eng.pr.md +4 -4
- package/workflows/engineering/eng.pre-pr.md +4 -4
- package/workflows/engineering/eng.rpa.robot.md +4 -2
- package/workflows/engineering/eng.start.md +17 -16
- package/workflows/product/prod.roadmap.preview.md +4 -1
- package/workflows/product/prod.spec.ard.md +7 -3
- package/workflows/product/prod.spec.breakdown.md +7 -3
- package/workflows/product/prod.spec.clarify.md +5 -1
- package/workflows/product/prod.spec.discovery.md +7 -3
- package/workflows/product/prod.spec.epic.md +13 -11
- package/workflows/product/prod.spec.frd.md +15 -13
- package/workflows/product/prod.spec.issue.md +5 -1
- package/workflows/product/prod.spec.md +3 -1
- package/workflows/product/prod.spec.prd.md +15 -13
- package/workflows/warm-up.md +3 -3
- package/bin/lib/docs/redis-cache.js +0 -49
- package/bin/lib/vcs/fetch-raw.js +0 -42
|
@@ -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
|
|
78
|
+
Para buscar documentação do produto no 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
|
|
21
|
-
│ ├── create-merge.js, create-issue.js
|
|
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/ #
|
|
27
|
-
│ ├── validate-frontmatter.js # Valida/extrai frontmatter
|
|
28
|
-
│ ├──
|
|
29
|
-
│ ├──
|
|
30
|
-
│ └──
|
|
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
|
|
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 `
|
|
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
|
|
88
|
+
## Os 3 "scripts standalone" (`vcs/*.js`, `tasks/comment.js`): dupla forma
|
|
89
89
|
|
|
90
|
-
`create-merge.js`, `create-issue.js
|
|
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
|
|
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
|
|
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)` (
|
|
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,106 @@
|
|
|
1
|
-
import { loadEnv, resolveEnvPath } from "../lib/env-loader.js";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
|
-
import {
|
|
2
|
+
import { openStorageFromEnv } from "../lib/docs/fetch-file.js";
|
|
3
|
+
import { publishFile, VALID_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
|
|
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 (
|
|
12
|
-
* @param {string} flags.
|
|
13
|
-
* @param {string} flags.
|
|
14
|
-
* @param {string} flags.
|
|
12
|
+
* @param {string} flags.tipo - Tipo do documento (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}-{id}-{nome}
|
|
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 {
|
|
22
|
+
const {
|
|
23
|
+
file,
|
|
24
|
+
tipo,
|
|
25
|
+
feature,
|
|
26
|
+
produto,
|
|
27
|
+
squad: squadOverride,
|
|
28
|
+
workspace: workspaceOverride,
|
|
29
|
+
} = flags;
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
if (!file || !tipo || !feature) {
|
|
31
|
+
if (!file || !tipo) {
|
|
24
32
|
logger.error("❌ Parâmetros obrigatórios faltando\n");
|
|
25
33
|
logger.error(
|
|
26
|
-
|
|
34
|
+
`Uso: jarvis docs publish --file <path> --tipo <${VALID_TYPES.join("|")}> [--produto <produto>] [--feature <slug>] [--squad <squad>]\n`
|
|
27
35
|
);
|
|
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");
|
|
36
|
+
logger.error("Exemplo (o nome do arquivo já segue {tipo}-{id}-{nome}):");
|
|
37
|
+
logger.error(" jarvis docs publish --file ./docs/product/prd-001-checkout.md --tipo prd");
|
|
33
38
|
logger.error("");
|
|
34
|
-
logger.error(" #
|
|
35
|
-
logger.error(
|
|
36
|
-
|
|
37
|
-
|
|
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");
|
|
39
|
+
logger.error(" # Arquivo fora do padrão de nome:");
|
|
40
|
+
logger.error(
|
|
41
|
+
" jarvis docs publish --file ./docs/engineering/swagger/api.yaml --tipo swagger --feature api-wallet"
|
|
42
|
+
);
|
|
47
43
|
process.exit(1);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
const validTypes = ["prd", "frd", "ard", "rfc", "swagger", "qa-report"];
|
|
52
|
-
if (!validTypes.includes(tipo)) {
|
|
46
|
+
if (!VALID_TYPES.includes(tipo)) {
|
|
53
47
|
logger.error(`❌ Tipo inválido: ${tipo}`);
|
|
54
|
-
logger.error(`Tipos válidos: ${
|
|
48
|
+
logger.error(`Tipos válidos: ${VALID_TYPES.join(", ")}`);
|
|
55
49
|
process.exit(1);
|
|
56
50
|
}
|
|
57
51
|
|
|
58
|
-
// Validar se arquivo existe
|
|
59
52
|
if (!existsSync(file)) {
|
|
60
53
|
logger.error(`❌ Arquivo não encontrado: ${file}`);
|
|
61
54
|
process.exit(1);
|
|
62
55
|
}
|
|
63
56
|
|
|
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);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Carregar ENV.md
|
|
77
57
|
let env;
|
|
58
|
+
let storage;
|
|
78
59
|
try {
|
|
79
|
-
env =
|
|
60
|
+
({ env, storage } = openStorageFromEnv(flags));
|
|
80
61
|
} catch (error) {
|
|
81
|
-
logger.error("❌
|
|
62
|
+
logger.error("❌ Central docs:", error.message);
|
|
82
63
|
process.exit(1);
|
|
83
64
|
}
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
logger.error("
|
|
88
|
-
logger.error("\nConfigure a URL do repositório central:");
|
|
89
|
-
logger.error(" CENTRAL_DOCS_REPO=https://gitlab.com/org/central-docs.git");
|
|
66
|
+
if (!storage) {
|
|
67
|
+
logger.error("❌ Central docs desligado (CENTRAL_DOCS_STORAGE vazio no ENV.md).");
|
|
68
|
+
logger.error(" Configure-o com o /jarvis-init.");
|
|
90
69
|
process.exit(1);
|
|
91
70
|
}
|
|
92
71
|
|
|
93
|
-
// Usar override de squad/workspace se fornecido, senão usar do ENV.md
|
|
94
72
|
const squad = squadOverride || env.SQUAD;
|
|
95
|
-
const
|
|
73
|
+
const product = produto || workspaceOverride || env.WORKSPACE;
|
|
96
74
|
|
|
97
|
-
if (!squad || !
|
|
75
|
+
if (!squad || !product) {
|
|
98
76
|
logger.error("❌ SQUAD ou WORKSPACE não definidos");
|
|
99
|
-
logger.error("
|
|
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");
|
|
77
|
+
logger.error(" Defina no ENV.md ou passe --squad <squad> --produto <produto>");
|
|
106
78
|
process.exit(1);
|
|
107
79
|
}
|
|
108
80
|
|
|
109
|
-
logger.info(`📤 Publicando ${tipo.toUpperCase()} ${
|
|
81
|
+
logger.info(`📤 Publicando ${tipo.toUpperCase()} (${file})...\n`);
|
|
110
82
|
|
|
111
83
|
try {
|
|
112
|
-
await publishFile({
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
});
|
|
84
|
+
await publishFile({ storage, localFile: file, tipo, squad, produto: product, feature });
|
|
85
|
+
logger.info("");
|
|
86
|
+
logger.info("✅ Publicação concluída!");
|
|
122
87
|
} catch (error) {
|
|
123
88
|
logger.error("\n❌ Erro ao publicar documento\n");
|
|
124
89
|
logger.error(error.message);
|
|
125
90
|
|
|
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
91
|
if (error.message.includes("Campos obrigatórios")) {
|
|
135
|
-
logger.error(
|
|
136
|
-
|
|
92
|
+
logger.error(
|
|
93
|
+
`\n💡 Verifique o frontmatter do documento: o tipo ${tipo} exige campos específicos.`
|
|
94
|
+
);
|
|
137
95
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
96
|
+
if (storage.isGit) {
|
|
97
|
+
logger.error(
|
|
98
|
+
"\n💡 Central docs com git: confira se a pasta está limpa (git status), se você tem permissão de push"
|
|
99
|
+
);
|
|
100
|
+
logger.error(
|
|
101
|
+
" e se o token do vendor está configurado (GitLab .npmrc, GitHub gh auth, BITBUCKET_TOKEN)."
|
|
102
|
+
);
|
|
142
103
|
}
|
|
143
|
-
|
|
144
|
-
logger.error("Para mais detalhes, execute com --verbose");
|
|
145
|
-
|
|
146
104
|
process.exit(1);
|
|
147
105
|
}
|
|
148
106
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 = ["prd", "frd", "feat", "epic", "story", "task", "ard", "rfc"];
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
|
-
* Sincroniza documentação do
|
|
7
|
-
* Executado automaticamente em /warm-up
|
|
7
|
+
* Sincroniza a documentação do Central docs (lê `{SQUAD}/index.md` pelo adaptador).
|
|
8
|
+
* Executado automaticamente em /warm-up.
|
|
8
9
|
*
|
|
9
10
|
* @param {Object} flags - Flags do comando
|
|
10
11
|
* @param {boolean} flags.silent - Modo silencioso (nem erros aparecem)
|
|
@@ -15,87 +16,53 @@ import { logger, configureFromFlags } from "../lib/utils/logger.js";
|
|
|
15
16
|
export async function docsSync(flags = {}) {
|
|
16
17
|
configureFromFlags(flags);
|
|
17
18
|
|
|
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
19
|
let env;
|
|
20
|
+
let storage;
|
|
32
21
|
try {
|
|
33
|
-
env =
|
|
22
|
+
({ env, storage } = openStorageFromEnv(flags));
|
|
34
23
|
} catch (error) {
|
|
35
|
-
logger.error("❌
|
|
24
|
+
logger.error("❌ Central docs:", error.message);
|
|
36
25
|
process.exit(1);
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
logger.info("ℹ️ Central docs não configurado (CENTRAL_DOCS_REPO vazio)");
|
|
28
|
+
if (!storage) {
|
|
29
|
+
logger.info("ℹ️ Central docs desligado (CENTRAL_DOCS_STORAGE vazio)");
|
|
42
30
|
return;
|
|
43
31
|
}
|
|
44
32
|
|
|
45
|
-
const { SQUAD
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
logger.error("❌ SQUAD ou WORKSPACE não definidos no ENV.md");
|
|
33
|
+
const { SQUAD } = env;
|
|
34
|
+
if (!SQUAD) {
|
|
35
|
+
logger.error("❌ SQUAD não definido no ENV.md");
|
|
49
36
|
process.exit(1);
|
|
50
37
|
}
|
|
51
38
|
|
|
52
|
-
logger.info(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const indexPath = `${SQUAD}/index.md`;
|
|
39
|
+
logger.info(
|
|
40
|
+
`🔄 Sincronizando docs do squad ${SQUAD} (${storage.kind}${storage.isGit ? " + git" : ""})...`
|
|
41
|
+
);
|
|
56
42
|
|
|
57
43
|
try {
|
|
58
|
-
const content = await
|
|
59
|
-
repo: env.CENTRAL_DOCS_REPO,
|
|
60
|
-
cacheTtl: env.CENTRAL_DOCS_CACHE_TTL || "3600",
|
|
61
|
-
});
|
|
62
|
-
|
|
44
|
+
const content = await storage.fetch(`${SQUAD}/index.md`);
|
|
63
45
|
logger.info("✅ Docs sincronizados");
|
|
64
46
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
logger.debug(content);
|
|
68
|
-
}
|
|
47
|
+
logger.debug("\n📄 Conteúdo do index.md:\n");
|
|
48
|
+
logger.debug(content);
|
|
69
49
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const rfcCount = (content.match(/\[rfc-.*\.md\]/g) || []).length;
|
|
50
|
+
const counts = COUNTED_TYPES.map((tipo) => [
|
|
51
|
+
tipo,
|
|
52
|
+
(content.match(new RegExp(`\\[${tipo}-[^\\]]*\\]`, "g")) || []).length,
|
|
53
|
+
]).filter(([, n]) => n > 0);
|
|
54
|
+
const total = counts.reduce((sum, [, n]) => sum + n, 0);
|
|
76
55
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
}
|
|
56
|
+
if (total > 0) {
|
|
57
|
+
logger.info(`📊 ${total} documentos disponíveis:`);
|
|
58
|
+
for (const [tipo, n] of counts) logger.info(` - ${n} ${tipo.toUpperCase()}(s)`);
|
|
86
59
|
}
|
|
87
60
|
} catch (error) {
|
|
88
61
|
logger.error("❌ Erro ao sincronizar docs:", error.message);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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`);
|
|
62
|
+
if (error.code === 404) {
|
|
63
|
+
logger.error("\n💡 O squad ainda não tem índice no Central docs:");
|
|
64
|
+
logger.error(` ${SQUAD}/index.md (é criado na primeira publicação: jarvis docs publish)`);
|
|
97
65
|
}
|
|
98
|
-
|
|
99
66
|
process.exit(1);
|
|
100
67
|
}
|
|
101
68
|
}
|
package/bin/lib/cli/program.js
CHANGED
|
@@ -28,7 +28,6 @@ import { tokens } from "../../commands/tokens.js";
|
|
|
28
28
|
import { map } from "../../commands/map.js";
|
|
29
29
|
import { runCreateMerge } from "../vcs/create-merge.js";
|
|
30
30
|
import { runCreateIssue } from "../vcs/create-issue.js";
|
|
31
|
-
import { runFetchRaw } from "../vcs/fetch-raw.js";
|
|
32
31
|
import { runComment } from "../tasks/comment.js";
|
|
33
32
|
import { fetchFile } from "../docs/fetch-file.js";
|
|
34
33
|
import { formatIDEsList } from "../config/ide-config.js";
|
|
@@ -112,33 +111,38 @@ export function buildProgram() {
|
|
|
112
111
|
const docs = cmd(program, "docs").description("Sincronizar ou publicar documentação");
|
|
113
112
|
|
|
114
113
|
const docsSyncCmd = cmd(docs, "sync").description(
|
|
115
|
-
"Sincronizar docs do
|
|
114
|
+
"Sincronizar docs do Central docs pelo adaptador de CENTRAL_DOCS_STORAGE (usado por /warm-up)"
|
|
116
115
|
);
|
|
117
116
|
withEnvOptions(withLogOptions(docsSyncCmd)).action((options) => docsSync(toLegacyFlags(options)));
|
|
118
117
|
|
|
119
|
-
cmd(docs, "fetch")
|
|
118
|
+
const docsFetchCmd = cmd(docs, "fetch")
|
|
120
119
|
.description(
|
|
121
|
-
"Buscar um arquivo do
|
|
120
|
+
"Buscar um arquivo do Central docs pelo adaptador de CENTRAL_DOCS_STORAGE (com git: pull antes de ler)"
|
|
122
121
|
)
|
|
123
|
-
.argument("<file-path>", "
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
122
|
+
.argument("<file-path>", "Caminho do arquivo dentro do Central docs (ex: CORE/index.md)");
|
|
123
|
+
withEnvOptions(docsFetchCmd).action(async (filePath, options) => {
|
|
124
|
+
try {
|
|
125
|
+
const content = await fetchFile(filePath, { flags: toLegacyFlags(options) });
|
|
126
|
+
process.stdout.write(content);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(error.message);
|
|
129
|
+
process.exit(error.code === 404 ? 2 : 1);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
134
132
|
|
|
135
133
|
const docsPublishCmd = cmd(docs, "publish")
|
|
136
|
-
.description(
|
|
134
|
+
.description(
|
|
135
|
+
"Publicar documento no Central docs (pasta: copia e regenera o índice; com git: branch + PR)"
|
|
136
|
+
)
|
|
137
137
|
.option("--file <path>", "Caminho do arquivo local")
|
|
138
|
-
.option("--tipo <tipo>", "prd|frd|ard|rfc|swagger|qa-report")
|
|
139
|
-
.option("--
|
|
138
|
+
.option("--tipo <tipo>", "prd|frd|feat|epic|story|task|ard|rfc|swagger|qa-report")
|
|
139
|
+
.option("--produto <produto>", "Pasta do produto no Central docs (padrão: WORKSPACE do ENV.md)")
|
|
140
|
+
.option("--feature <slug>", "Só se o nome do arquivo fugir do padrão {tipo}-{id}-{nome}")
|
|
140
141
|
.option("--squad <squad>", "Override do SQUAD do ENV.md")
|
|
141
|
-
.option(
|
|
142
|
+
.option(
|
|
143
|
+
"--workspace <workspace>",
|
|
144
|
+
"Override do WORKSPACE do ENV.md (mesmo efeito de --produto)"
|
|
145
|
+
);
|
|
142
146
|
withEnvOptions(withLogOptions(docsPublishCmd)).action((options) =>
|
|
143
147
|
docsPublish(toLegacyFlags(options))
|
|
144
148
|
);
|
|
@@ -201,12 +205,6 @@ export function buildProgram() {
|
|
|
201
205
|
runCreateIssue({ repo: options.repo, title: options.title, bodyFile: options.bodyFile })
|
|
202
206
|
);
|
|
203
207
|
|
|
204
|
-
cmd(vcs, "fetch-raw")
|
|
205
|
-
.description("Buscar o conteúdo bruto de um arquivo no central-docs (lê CENTRAL_DOCS_REPO)")
|
|
206
|
-
.argument("[file-path]")
|
|
207
|
-
.argument("[ref]", "Branch/ref", "main")
|
|
208
|
-
.action((filePath, ref) => runFetchRaw(filePath, ref));
|
|
209
|
-
|
|
210
208
|
const tasks = cmd(program, "tasks").description("Adapters de task manager");
|
|
211
209
|
|
|
212
210
|
cmd(tasks, "comment")
|
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
* comportamento, sem subprocess: chama `fetchRawFile` de `vcs/api.js` direto.
|
|
2
|
+
* @fileoverview Leitura do Central docs pelo adaptador configurado
|
|
3
|
+
* (`CENTRAL_DOCS_STORAGE`), venha o conteúdo de onde vier.
|
|
5
4
|
* @module docs/fetch-file
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { loadEnv, resolveEnvPath } from "../env-loader.js";
|
|
8
|
+
import { getStorage } from "./storage/index.js";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @param {
|
|
11
|
+
* Carrega o ENV.md e devolve o adaptador do Central docs.
|
|
12
|
+
* @param {Object} [flags] - `envFile`/`ide` (mesmas opções dos outros comandos)
|
|
14
13
|
* @param {Object} [opts]
|
|
15
|
-
* @param {string} [opts.
|
|
16
|
-
* @param {
|
|
17
|
-
* @returns {
|
|
18
|
-
* @throws {Error} sem `filePath`, sem `repo`, ou erro do adapter (404/401/403 — ver `fetchRawFile`)
|
|
14
|
+
* @param {string} [opts.cwd]
|
|
15
|
+
* @param {Object} [opts.deps] - Dependências injetáveis do adaptador (testes)
|
|
16
|
+
* @returns {{ env: Record<string, string>, root: string, storage: Object|null }}
|
|
19
17
|
*/
|
|
20
|
-
export
|
|
18
|
+
export function openStorageFromEnv(flags = {}, { cwd = process.cwd(), deps } = {}) {
|
|
19
|
+
const { path, root } = resolveEnvPath(cwd, flags);
|
|
20
|
+
const env = loadEnv(cwd, path);
|
|
21
|
+
return { env, root, storage: getStorage(env, { root, deps }) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Lê um arquivo do Central docs.
|
|
26
|
+
* @param {string} filePath - Caminho dentro do Central docs (ex: `CORE/index.md`)
|
|
27
|
+
* @param {Object} [opts]
|
|
28
|
+
* @param {Object} [opts.storage] - Adaptador já aberto; sem ele, abre pelo ENV.md
|
|
29
|
+
* @param {Object} [opts.flags]
|
|
30
|
+
* @returns {Promise<string>}
|
|
31
|
+
* @throws {Error} sem `filePath`, Central docs desligado, ou `code = 404`
|
|
32
|
+
*/
|
|
33
|
+
export async function fetchFile(filePath, opts = {}) {
|
|
21
34
|
if (!filePath) {
|
|
22
35
|
throw new Error("fetchFile: file-path é obrigatório");
|
|
23
36
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
throw new Error("CENTRAL_DOCS_REPO não definido no ENV.md");
|
|
37
|
+
const storage = opts.storage ?? openStorageFromEnv(opts.flags).storage;
|
|
38
|
+
if (!storage) {
|
|
39
|
+
throw new Error("Central docs desligado (CENTRAL_DOCS_STORAGE vazio no ENV.md).");
|
|
28
40
|
}
|
|
29
|
-
|
|
30
|
-
const cacheKey = `docs:${filePath}:${ref}`;
|
|
31
|
-
const cached = redisGet(cacheKey);
|
|
32
|
-
if (cached) return cached;
|
|
33
|
-
|
|
34
|
-
const content = await fetchRawFile(repo, filePath, ref);
|
|
35
|
-
|
|
36
|
-
redisSetex(cacheKey, opts.cacheTtl ?? process.env.CENTRAL_DOCS_CACHE_TTL ?? "3600", content);
|
|
37
|
-
|
|
38
|
-
return content;
|
|
41
|
+
return storage.fetch(filePath);
|
|
39
42
|
}
|