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.
- package/agents/engineering/eng.docs-consolidator.agent.md +81 -0
- package/agents/engineering/qa/eng.qa.test-planner.md +7 -7
- package/agents/product/prod.discovery-interviewer.md +1 -1
- package/bin/README.md +14 -14
- package/bin/commands/docs-publish.js +61 -93
- package/bin/commands/docs-sync.js +31 -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 +98 -159
- package/bin/lib/docs/storage/build-index.js +108 -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 +18 -4
- package/bin/lib/vcs/api.js +0 -125
- package/package.json +2 -2
- package/rules/AGENTS.md +12 -9
- package/rules/engineering/eng.docs-scraping-rules.md +4 -4
- package/rules/engineering/eng.integrations-rules.md +3 -2
- package/rules/engineering/rpa/eng.rpa-rules.md +1 -1
- package/rules/product/prod-rules.md +5 -6
- package/rules-on-demand/engineering/eng.pr-rules.md +9 -31
- package/rules-on-demand/engineering/eng.pre-pr-rules.md +12 -19
- package/rules-on-demand/product/prod.guided-rules.md +104 -0
- package/skills/eng-backend-arch-c4/SKILL.md +3 -3
- package/skills/eng-global-docs-write/SKILL.md +1 -1
- package/skills/eng-qa-planner/SKILL.md +5 -5
- package/skills/jarvis-docs-central/README.md +51 -275
- package/skills/jarvis-docs-central/SKILL.md +114 -301
- package/skills/jarvis-init/SKILL.md +81 -8
- package/templates/ENV-template.md +28 -7
- package/templates/engineering/ARD-template.md +40 -7
- package/templates/engineering/architecture-template.md +1 -0
- package/templates/engineering/central-delivery-template.md +65 -0
- package/templates/engineering/central-feature-template.md +86 -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 +4 -4
- package/workflows/engineering/eng.create-ard-from-code.md +15 -41
- package/workflows/engineering/eng.create-ard.md +22 -44
- package/workflows/engineering/eng.create-rfc.md +10 -36
- package/workflows/engineering/eng.pr.md +35 -66
- package/workflows/engineering/eng.pre-pr.md +52 -89
- package/workflows/engineering/eng.rpa.robot.md +9 -7
- package/workflows/engineering/eng.spec-consolidate.md +140 -0
- package/workflows/engineering/eng.start.md +23 -22
- package/workflows/engineering/eng.work.md +1 -1
- package/workflows/engineering/qa/eng.qa-refinement-entry.md +1 -1
- package/workflows/engineering/qa/eng.qa-release-signoff.md +3 -3
- package/workflows/engineering/qa/eng.qa-sprint-planning.md +2 -2
- package/workflows/product/prod.roadmap.preview.md +4 -1
- package/workflows/product/prod.spec.ard.md +9 -5
- package/workflows/product/prod.spec.breakdown.md +8 -4
- 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 +12 -40
- package/workflows/product/prod.spec.frd.md +13 -39
- package/workflows/product/prod.spec.issue.md +5 -1
- package/workflows/product/prod.spec.md +3 -1
- package/workflows/product/prod.spec.prd.md +13 -40
- package/workflows/warm-up.md +10 -12
- package/bin/lib/docs/redis-cache.js +0 -49
- package/bin/lib/vcs/fetch-raw.js +0 -42
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Adaptador `filesystem` do Central docs: uma pasta do disco
|
|
3
|
+
* (dentro do workspace ou em qualquer caminho). Se a pasta tiver `.git` (clone
|
|
4
|
+
* de um repositório de documentação), liga a camada git: `pull` antes de ler e
|
|
5
|
+
* branch + commit + push + PR ao publicar.
|
|
6
|
+
*
|
|
7
|
+
* Todo comando externo usa `execFileSync` com argumentos (sem shell), e todo
|
|
8
|
+
* caminho recebido é resolvido dentro da pasta (sem `..`).
|
|
9
|
+
* @module docs/storage/filesystem
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
15
|
+
import { createMergeRequest as defaultCreateMergeRequest } from "../../vcs/api.js";
|
|
16
|
+
import { logger } from "../../utils/logger.js";
|
|
17
|
+
import { buildIndex, walkFiles } from "./build-index.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Executa `git` na pasta informada, sem shell.
|
|
21
|
+
* @param {string[]} args
|
|
22
|
+
* @param {string} cwd
|
|
23
|
+
* @returns {string} stdout sem espaços nas pontas
|
|
24
|
+
*/
|
|
25
|
+
export function runGit(args, cwd) {
|
|
26
|
+
return execFileSync("git", args, {
|
|
27
|
+
cwd,
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve um caminho relativo dentro de `root`, rejeitando o que sair dela.
|
|
35
|
+
* @param {string} root
|
|
36
|
+
* @param {string} rel
|
|
37
|
+
* @returns {string} caminho absoluto
|
|
38
|
+
* @throws {Error} caminho absoluto, com `..` que escape da pasta, ou vazio
|
|
39
|
+
*/
|
|
40
|
+
export function safePath(root, rel) {
|
|
41
|
+
if (!rel || isAbsolute(rel)) {
|
|
42
|
+
throw new Error(`Caminho inválido para o Central docs: "${rel}"`);
|
|
43
|
+
}
|
|
44
|
+
const full = resolve(root, rel);
|
|
45
|
+
const fromRoot = relative(root, full);
|
|
46
|
+
if (fromRoot === "" || fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
|
|
47
|
+
throw new Error(`Caminho fora da pasta do Central docs: "${rel}"`);
|
|
48
|
+
}
|
|
49
|
+
return full;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Object} cfg
|
|
54
|
+
* @param {string} cfg.location - Pasta do Central docs (absoluta)
|
|
55
|
+
* @param {string} [cfg.ref] - Branch lida no `pull` (CENTRAL_DOCS_REF)
|
|
56
|
+
* @param {string} [cfg.targetBranch] - Branch base do PR (CENTRAL_DOCS_TARGET_BRANCH)
|
|
57
|
+
* @param {string} [cfg.reviewers] - Revisores (vão na descrição do PR)
|
|
58
|
+
* @param {Object} [cfg.deps] - `runGit` e `createMergeRequest` injetáveis (testes)
|
|
59
|
+
*/
|
|
60
|
+
export function createFilesystemStorage({
|
|
61
|
+
location,
|
|
62
|
+
ref = "main",
|
|
63
|
+
targetBranch = "main",
|
|
64
|
+
reviewers = "",
|
|
65
|
+
deps = {},
|
|
66
|
+
}) {
|
|
67
|
+
const git = deps.runGit ?? runGit;
|
|
68
|
+
const createMergeRequest = deps.createMergeRequest ?? defaultCreateMergeRequest;
|
|
69
|
+
const isGit = existsSync(join(location, ".git"));
|
|
70
|
+
let pulled = false;
|
|
71
|
+
|
|
72
|
+
/** `git pull --ff-only` uma vez por instância; falha vira aviso e a leitura segue local. */
|
|
73
|
+
function pullOnce() {
|
|
74
|
+
if (!isGit || pulled) return;
|
|
75
|
+
pulled = true;
|
|
76
|
+
try {
|
|
77
|
+
git(["pull", "--ff-only", "origin", ref], location);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.warn(`⚠️ Não foi possível atualizar o Central docs (git pull): ${error.message.split("\n")[0]}`);
|
|
80
|
+
logger.warn(" Usando a cópia local.");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ensureLocation() {
|
|
85
|
+
if (!existsSync(location)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Pasta do Central docs não encontrada: ${location}\nCrie a pasta ou ajuste CENTRAL_DOCS_LOCATION no ENV.md (/jarvis-init).`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
kind: "filesystem",
|
|
94
|
+
location,
|
|
95
|
+
isGit,
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Lê um documento.
|
|
99
|
+
* @param {string} path - Caminho dentro do Central docs (ex: `CORE/index.md`)
|
|
100
|
+
* @returns {Promise<string>}
|
|
101
|
+
* @throws {Error} com `code = 404` se o arquivo não existir
|
|
102
|
+
*/
|
|
103
|
+
async fetch(path) {
|
|
104
|
+
ensureLocation();
|
|
105
|
+
pullOnce();
|
|
106
|
+
const full = safePath(location, path);
|
|
107
|
+
if (!existsSync(full)) {
|
|
108
|
+
const err = new Error(`Arquivo não encontrado no Central docs: ${path}`);
|
|
109
|
+
err.code = 404;
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
return readFileSync(full, "utf-8");
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Lista os `.md` sob um prefixo.
|
|
117
|
+
* @param {string} prefix
|
|
118
|
+
* @returns {Promise<string[]>} caminhos relativos à pasta, com `/`
|
|
119
|
+
*/
|
|
120
|
+
async list(prefix) {
|
|
121
|
+
ensureLocation();
|
|
122
|
+
pullOnce();
|
|
123
|
+
return walkFiles(safePath(location, prefix))
|
|
124
|
+
.filter((f) => f.endsWith(".md"))
|
|
125
|
+
.map((f) => relative(location, f).split(sep).join("/"));
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Regenera `{squad}/index.md`. Com `.git` e `scripts/build-index.sh` no repositório,
|
|
130
|
+
* usa o script dele (o CI do repositório confere o índice com ele).
|
|
131
|
+
* @param {string} squad
|
|
132
|
+
* @returns {string} caminho relativo do índice
|
|
133
|
+
*/
|
|
134
|
+
buildIndex(squad) {
|
|
135
|
+
const script = join(location, "scripts", "build-index.sh");
|
|
136
|
+
if (isGit && existsSync(script)) {
|
|
137
|
+
execFileSync("bash", [script], { cwd: location, stdio: ["ignore", "pipe", "pipe"] });
|
|
138
|
+
} else {
|
|
139
|
+
buildIndex(location, squad);
|
|
140
|
+
}
|
|
141
|
+
return `${squad}/index.md`;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Grava um documento e regenera o índice do squad.
|
|
146
|
+
* Sem `.git`: copia e pronto. Com `.git`: branch → cópia → índice → commit → push → PR.
|
|
147
|
+
* @param {Object} p
|
|
148
|
+
* @param {string} p.localFile - Arquivo de origem
|
|
149
|
+
* @param {string} p.destPath - Destino dentro do Central docs (`{SQUAD}/{área}/{produto}/{arquivo}`)
|
|
150
|
+
* @param {string} p.squad
|
|
151
|
+
* @param {string} p.tipo
|
|
152
|
+
* @returns {Promise<{ destPath: string, indexPath: string, sha?: string, branch?: string, mr?: Object }>}
|
|
153
|
+
*/
|
|
154
|
+
async publish({ localFile, destPath, squad, tipo }) {
|
|
155
|
+
ensureLocation();
|
|
156
|
+
const dest = safePath(location, destPath);
|
|
157
|
+
|
|
158
|
+
if (!isGit) {
|
|
159
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
160
|
+
copyFileSync(localFile, dest);
|
|
161
|
+
const indexPath = this.buildIndex(squad);
|
|
162
|
+
return { destPath, indexPath };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const branch = `docs/${destPath.replace(extname(destPath), "")}`;
|
|
166
|
+
git(["checkout", targetBranch], location);
|
|
167
|
+
try {
|
|
168
|
+
git(["pull", "--ff-only", "origin", targetBranch], location);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.warn(`⚠️ git pull falhou (${error.message.split("\n")[0]}); seguindo com a cópia local.`);
|
|
171
|
+
}
|
|
172
|
+
git(["checkout", "-b", branch], location);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
176
|
+
copyFileSync(localFile, dest);
|
|
177
|
+
const indexPath = this.buildIndex(squad);
|
|
178
|
+
git(["add", "--", destPath, indexPath], location);
|
|
179
|
+
git(["commit", "-m", `docs(${squad}): add ${tipo} ${basename(destPath)}`], location);
|
|
180
|
+
const sha = git(["rev-parse", "HEAD"], location);
|
|
181
|
+
|
|
182
|
+
git(["push", "-u", "origin", branch], location);
|
|
183
|
+
const remoteUrl = git(["remote", "get-url", "origin"], location);
|
|
184
|
+
let description = `## Documento\n\n- **Tipo**: ${tipo}\n- **Arquivo**: \`${destPath}\``;
|
|
185
|
+
if (reviewers) description += `\n- **Revisores**: ${reviewers}`;
|
|
186
|
+
const mr = await createMergeRequest(remoteUrl, {
|
|
187
|
+
source: branch,
|
|
188
|
+
target: targetBranch,
|
|
189
|
+
title: `docs(${squad}): ${tipo} ${basename(destPath)}`,
|
|
190
|
+
description,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return { destPath, indexPath, sha, branch, mr };
|
|
194
|
+
} finally {
|
|
195
|
+
try {
|
|
196
|
+
git(["checkout", targetBranch], location);
|
|
197
|
+
} catch {
|
|
198
|
+
// a branch de trabalho fica; o erro original (se houver) é o que importa
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fábrica dos adaptadores de armazenamento do Central docs.
|
|
3
|
+
* O valor de `CENTRAL_DOCS_STORAGE` escolhe o adaptador; `CENTRAL_DOCS_LOCATION`
|
|
4
|
+
* diz onde ele guarda. Hoje só existe o `filesystem` (com camada git opcional);
|
|
5
|
+
* S3 e Google Drive são as issues #144 e #145.
|
|
6
|
+
* @module docs/storage
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isAbsolute, resolve } from "node:path";
|
|
10
|
+
import { createFilesystemStorage } from "./filesystem.js";
|
|
11
|
+
|
|
12
|
+
/** @constant {ReadonlyArray<string>} Adaptadores disponíveis */
|
|
13
|
+
export const SUPPORTED_STORAGES = Object.freeze(["filesystem"]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Erro de configuração do Central docs (variável ausente, valor não suportado).
|
|
17
|
+
*/
|
|
18
|
+
export class StorageConfigError extends Error {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve `CENTRAL_DOCS_LOCATION` a partir da pasta do workspace (a que contém `$IDE/`).
|
|
22
|
+
* @param {string} location
|
|
23
|
+
* @param {string} root - Pasta do workspace
|
|
24
|
+
* @returns {string} caminho absoluto
|
|
25
|
+
*/
|
|
26
|
+
export function resolveLocation(location, root) {
|
|
27
|
+
return isAbsolute(location) ? location : resolve(root, location);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Devolve o adaptador configurado no ENV.md, ou `null` se o Central docs estiver desligado.
|
|
32
|
+
*
|
|
33
|
+
* @param {Record<string, string>} env - Variáveis do ENV.md (`loadEnv`)
|
|
34
|
+
* @param {Object} opts
|
|
35
|
+
* @param {string} opts.root - Pasta do workspace (`resolveEnvPath(...).root`)
|
|
36
|
+
* @param {Object} [opts.deps] - Dependências injetáveis (testes): `runGit`, `createMergeRequest`
|
|
37
|
+
* @returns {ReturnType<typeof createFilesystemStorage> | null}
|
|
38
|
+
* @throws {StorageConfigError} valor de STORAGE não suportado, LOCATION ausente ou variável antiga
|
|
39
|
+
*/
|
|
40
|
+
export function getStorage(env, { root, deps } = {}) {
|
|
41
|
+
const storage = (env.CENTRAL_DOCS_STORAGE || "").trim().toLowerCase();
|
|
42
|
+
|
|
43
|
+
if (!storage) {
|
|
44
|
+
if (env.CENTRAL_DOCS_REPO) {
|
|
45
|
+
throw new StorageConfigError(
|
|
46
|
+
"CENTRAL_DOCS_REPO não é mais usado. Rode o /jarvis-init e escolha Upgrade (C) para migrar " +
|
|
47
|
+
"para CENTRAL_DOCS_STORAGE + CENTRAL_DOCS_LOCATION."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!SUPPORTED_STORAGES.includes(storage)) {
|
|
54
|
+
throw new StorageConfigError(
|
|
55
|
+
`Armazenamento "${storage}" ainda não suportado. Use: ${SUPPORTED_STORAGES.join(", ")}.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!env.CENTRAL_DOCS_LOCATION) {
|
|
60
|
+
throw new StorageConfigError(
|
|
61
|
+
"CENTRAL_DOCS_LOCATION não definido no ENV.md (pasta do Central docs)."
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (!root) {
|
|
65
|
+
throw new StorageConfigError("getStorage: pasta do workspace (root) não informada.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return createFilesystemStorage({
|
|
69
|
+
location: resolveLocation(env.CENTRAL_DOCS_LOCATION, root),
|
|
70
|
+
ref: env.CENTRAL_DOCS_REF || "main",
|
|
71
|
+
targetBranch: env.CENTRAL_DOCS_TARGET_BRANCH || "main",
|
|
72
|
+
reviewers: env.CENTRAL_DOCS_REVIEWERS || "",
|
|
73
|
+
deps,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -2,10 +2,10 @@ import { readFileSync } from 'node:fs'
|
|
|
2
2
|
import { parseFrontmatter, extractFrontmatterBlock } from '../utils/frontmatter.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Valida frontmatter YAML de documentos (PRD, FRD, ARD, RFC)
|
|
5
|
+
* Valida frontmatter YAML de documentos (PRD, FRD, épico, história, tarefa, ARD, RFC)
|
|
6
6
|
*
|
|
7
7
|
* @param {string} filePath - Caminho do arquivo
|
|
8
|
-
* @param {string} tipo - Tipo do documento (
|
|
8
|
+
* @param {string} tipo - Tipo do documento (ver VALID_TYPES em publish-file.js)
|
|
9
9
|
* @returns {Object} Metadados extraídos
|
|
10
10
|
* @throws {Error} Se frontmatter for inválido
|
|
11
11
|
*/
|
|
@@ -54,7 +54,13 @@ export function validateFrontmatter(filePath, tipo) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Validar status
|
|
57
|
-
|
|
57
|
+
// Produto (rules/product/prod-rules.md), ARD (templates/engineering/ARD-template.md),
|
|
58
|
+
// discovery (prod-discovery-template.md) e RFC (RFC-template.md)
|
|
59
|
+
const validStatuses = [
|
|
60
|
+
'icebox', 'in_review', 'backlog', 'in_progress', 'in_production', 'cancelled', 'deprecated',
|
|
61
|
+
'proposed', 'approved',
|
|
62
|
+
'Draft', 'In Review', 'Ready for Decision', 'Accepted', 'Rejected', 'Superseded'
|
|
63
|
+
]
|
|
58
64
|
|
|
59
65
|
if (metadata.status && !validStatuses.includes(metadata.status)) {
|
|
60
66
|
console.warn(
|
|
@@ -74,9 +80,17 @@ export function validateFrontmatter(filePath, tipo) {
|
|
|
74
80
|
*/
|
|
75
81
|
function getRequiredFields(tipo) {
|
|
76
82
|
const fields = {
|
|
83
|
+
// Consolidação do Central docs (/eng.spec-consolidate)
|
|
84
|
+
feature: ['id', 'name', 'status', 'product', 'updated_at'],
|
|
85
|
+
delivery: ['id', 'feature', 'task_key', 'pr', 'date'],
|
|
86
|
+
// Documentos de trabalho (validação usada fora do publish)
|
|
77
87
|
prd: ['id', 'name', 'version', 'status'],
|
|
78
88
|
frd: ['id', 'name', 'version', 'status', 'related_prd'],
|
|
79
|
-
|
|
89
|
+
feat: ['id', 'name', 'version', 'status', 'related_prd'],
|
|
90
|
+
epic: ['id', 'name', 'version', 'status', 'related_prd'],
|
|
91
|
+
story: ['id', 'name', 'status', 'related_prd'],
|
|
92
|
+
task: ['id', 'name', 'status'],
|
|
93
|
+
ard: ['id', 'name', 'version', 'status'],
|
|
80
94
|
rfc: ['Status', 'Criado em', 'Proponente'],
|
|
81
95
|
'qa-report': ['period', 'squad', 'version']
|
|
82
96
|
}
|
package/bin/lib/vcs/api.js
CHANGED
|
@@ -37,39 +37,6 @@ async function request(url, { method = "GET", headers = {}, body } = {}) {
|
|
|
37
37
|
return { ok: res.ok, status: res.status, json, text };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export async function fetchRawFile(repoUrl, filePath, ref = "main") {
|
|
41
|
-
const repo = parseRepoUrl(repoUrl);
|
|
42
|
-
const token = getVcsToken(repo.vendor);
|
|
43
|
-
const headers = authHeaders(repo.vendor, token);
|
|
44
|
-
const encodedPath = encodeURIComponent(filePath);
|
|
45
|
-
|
|
46
|
-
let url;
|
|
47
|
-
if (repo.vendor === "gitlab") {
|
|
48
|
-
url = `${repo.apiBase}/projects/${repo.projectPathEncoded}/repository/files/${encodedPath}/raw?ref=${encodeURIComponent(ref)}`;
|
|
49
|
-
} else if (repo.vendor === "github") {
|
|
50
|
-
url = `${repo.apiBase}/repos/${repo.owner}/${repo.repo}/contents/${filePath}?ref=${encodeURIComponent(ref)}`;
|
|
51
|
-
headers.Accept = "application/vnd.github.raw";
|
|
52
|
-
} else {
|
|
53
|
-
url = `${repo.apiBase}/repositories/${repo.owner}/${repo.repo}/src/${encodeURIComponent(ref)}/${filePath}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const res = await fetch(url, { headers });
|
|
57
|
-
if (res.status === 404) {
|
|
58
|
-
const err = new Error(`not_found:${filePath}`);
|
|
59
|
-
err.code = 404;
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
62
|
-
if (res.status === 401 || res.status === 403) {
|
|
63
|
-
const err = new Error(`unauthorized:${repo.vendor}`);
|
|
64
|
-
err.code = res.status;
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
if (!res.ok) {
|
|
68
|
-
throw new Error(`fetch failed ${res.status}: ${await res.text()}`);
|
|
69
|
-
}
|
|
70
|
-
return await res.text();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
40
|
export async function createMergeRequest(repoUrl, opts) {
|
|
74
41
|
const { source, target, title, description = "" } = opts;
|
|
75
42
|
const repo = parseRepoUrl(repoUrl);
|
|
@@ -141,98 +108,6 @@ export async function createMergeRequest(repoUrl, opts) {
|
|
|
141
108
|
};
|
|
142
109
|
}
|
|
143
110
|
|
|
144
|
-
/**
|
|
145
|
-
* Commit de um ou mais arquivos numa branch nova (docs publish).
|
|
146
|
-
* files: [{ path, contentBase64, action: 'create'|'update' }]
|
|
147
|
-
*/
|
|
148
|
-
export async function commitFiles(repoUrl, opts) {
|
|
149
|
-
const { branch, startBranch, message, files } = opts;
|
|
150
|
-
const repo = parseRepoUrl(repoUrl);
|
|
151
|
-
const token = getVcsToken(repo.vendor);
|
|
152
|
-
const headers = authHeaders(repo.vendor, token);
|
|
153
|
-
|
|
154
|
-
if (repo.vendor === "gitlab") {
|
|
155
|
-
const { ok, status, json } = await request(
|
|
156
|
-
`${repo.apiBase}/projects/${repo.projectPathEncoded}/repository/commits`,
|
|
157
|
-
{
|
|
158
|
-
method: "POST",
|
|
159
|
-
headers,
|
|
160
|
-
body: {
|
|
161
|
-
branch,
|
|
162
|
-
start_branch: startBranch,
|
|
163
|
-
commit_message: message,
|
|
164
|
-
actions: files.map((f) => ({
|
|
165
|
-
action: f.action || "create",
|
|
166
|
-
file_path: f.path,
|
|
167
|
-
content: f.contentBase64,
|
|
168
|
-
encoding: "base64",
|
|
169
|
-
})),
|
|
170
|
-
},
|
|
171
|
-
}
|
|
172
|
-
);
|
|
173
|
-
if (!ok || !json?.id) {
|
|
174
|
-
throw new Error(`Falha no commit GitLab (${status}): ${JSON.stringify(json)}`);
|
|
175
|
-
}
|
|
176
|
-
return { sha: json.id };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (repo.vendor === "github") {
|
|
180
|
-
const refRes = await request(
|
|
181
|
-
`${repo.apiBase}/repos/${repo.owner}/${repo.repo}/git/ref/heads/${startBranch}`,
|
|
182
|
-
{ headers }
|
|
183
|
-
);
|
|
184
|
-
if (!refRes.ok) {
|
|
185
|
-
throw new Error(`Branch ${startBranch} não encontrada no GitHub (${refRes.status})`);
|
|
186
|
-
}
|
|
187
|
-
const sha = refRes.json.object.sha;
|
|
188
|
-
await request(`${repo.apiBase}/repos/${repo.owner}/${repo.repo}/git/refs`, {
|
|
189
|
-
method: "POST",
|
|
190
|
-
headers,
|
|
191
|
-
body: { ref: `refs/heads/${branch}`, sha },
|
|
192
|
-
});
|
|
193
|
-
let lastSha = sha;
|
|
194
|
-
for (const file of files) {
|
|
195
|
-
const put = await request(
|
|
196
|
-
`${repo.apiBase}/repos/${repo.owner}/${repo.repo}/contents/${file.path}`,
|
|
197
|
-
{
|
|
198
|
-
method: "PUT",
|
|
199
|
-
headers,
|
|
200
|
-
body: {
|
|
201
|
-
message,
|
|
202
|
-
content: file.contentBase64,
|
|
203
|
-
branch,
|
|
204
|
-
},
|
|
205
|
-
}
|
|
206
|
-
);
|
|
207
|
-
if (!put.ok) {
|
|
208
|
-
throw new Error(
|
|
209
|
-
`Falha ao escrever ${file.path} no GitHub (${put.status}): ${JSON.stringify(put.json)}`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
lastSha = put.json.commit?.sha || lastSha;
|
|
213
|
-
}
|
|
214
|
-
return { sha: lastSha };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const form = new FormData();
|
|
218
|
-
form.set("branch", branch);
|
|
219
|
-
form.set("parents", startBranch);
|
|
220
|
-
form.set("message", message);
|
|
221
|
-
for (const file of files) {
|
|
222
|
-
const buf = Buffer.from(file.contentBase64, "base64");
|
|
223
|
-
form.set(file.path, new Blob([buf]));
|
|
224
|
-
}
|
|
225
|
-
const res = await fetch(`${repo.apiBase}/repositories/${repo.owner}/${repo.repo}/src`, {
|
|
226
|
-
method: "POST",
|
|
227
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
228
|
-
body: form,
|
|
229
|
-
});
|
|
230
|
-
if (!res.ok) {
|
|
231
|
-
throw new Error(`Falha no commit Bitbucket (${res.status}): ${await res.text()}`);
|
|
232
|
-
}
|
|
233
|
-
return { sha: "ok" };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
111
|
export async function createIssue(repoUrl, opts) {
|
|
237
112
|
const { title, body, labels = ["bug"] } = opts;
|
|
238
113
|
const repo = parseRepoUrl(repoUrl);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jarvis-ai-framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Jarvis framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"prepublishOnly": "echo 'Publishing jarvis-framework...'",
|
|
12
12
|
"publish:gitlab": "npm publish",
|
|
13
13
|
"test:comandos": "node test/comandos-docs.test.js",
|
|
14
|
-
"test:filtro": "node --test test/profile-filter.test.js test/token-report.test.js test/flow-map.test.js test/env-loader.test.js test/sync-engine.test.js test/vcs-api.test.js test/fetch-file.test.js test/publish-file.test.js",
|
|
14
|
+
"test:filtro": "node --test test/profile-filter.test.js test/token-report.test.js test/flow-map.test.js test/env-loader.test.js test/sync-engine.test.js test/vcs-api.test.js test/fetch-file.test.js test/publish-file.test.js test/docs-storage.test.js",
|
|
15
15
|
"lint": "eslint bin/ test/",
|
|
16
16
|
"lint:fix": "eslint bin/ test/ --fix",
|
|
17
17
|
"format": "prettier --write bin/ test/",
|
package/rules/AGENTS.md
CHANGED
|
@@ -24,14 +24,16 @@ rules/ # Sempre carregadas no início da sessão
|
|
|
24
24
|
└── prod-rules.md # Regras de especificacao
|
|
25
25
|
|
|
26
26
|
rules-on-demand/ # Lidas pelo workflow que as usa (não carregadas no início)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
├── engineering/
|
|
28
|
+
│ ├── eng.start-rules.md # Regras para /eng.start
|
|
29
|
+
│ ├── eng.plan-rules.md # Regras para /eng.plan
|
|
30
|
+
│ ├── eng.work-rules.md # Regras para /eng.work
|
|
31
|
+
│ ├── eng.pre-pr-rules.md # Regras para /eng.pre-pr
|
|
32
|
+
│ ├── eng.pr-rules.md # Regras para /eng.pr
|
|
33
|
+
│ ├── eng.tech-spec-rules.md # Regras para tech specs
|
|
34
|
+
│ └── eng.breakdown-subtasks-rules.md # Regras para o breakdown de subtarefas
|
|
35
|
+
└── product/
|
|
36
|
+
└── prod.guided-rules.md # Modo Developer-Guided (lida só com PROD_MODE=developer-guided)
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
### `rules/` x `rules-on-demand/`
|
|
@@ -46,7 +48,8 @@ O Claude Code carrega tudo de `.claude/rules/` no início da sessão: `rules/` f
|
|
|
46
48
|
1. .windsurf/rules / .cursor/rules (mais alta)
|
|
47
49
|
2. rules/engineering/eng-rules.md
|
|
48
50
|
3. rules-on-demand/engineering/eng.{comando}-rules.md
|
|
49
|
-
4. rules/product/prod-rules.md (
|
|
51
|
+
4. rules-on-demand/product/prod.guided-rules.md (só com PROD_MODE=developer-guided)
|
|
52
|
+
5. rules/product/prod-rules.md (mais baixa)
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
**Regra**: Regras mais especificas tem precedencia sobre regras gerais.
|
|
@@ -18,8 +18,8 @@ Aplicável sempre que um robô novo for implementado, um bug estrutural for reso
|
|
|
18
18
|
|
|
19
19
|
#### Localização
|
|
20
20
|
|
|
21
|
-
- Sempre criar em
|
|
22
|
-
- Exemplos:
|
|
21
|
+
- Sempre criar em `$ENG_DOCS/robots/{robot-tag}-robot.md`
|
|
22
|
+
- Exemplos: `$ENG_DOCS/robots/consulta-orgao-robot.md`, `$ENG_DOCS/robots/extrator-dados-robot.md`
|
|
23
23
|
|
|
24
24
|
#### Conteúdo obrigatório
|
|
25
25
|
|
|
@@ -44,7 +44,7 @@ Todo arquivo `{robot-tag}-robot.md` deve conter:
|
|
|
44
44
|
|
|
45
45
|
### Proibido
|
|
46
46
|
|
|
47
|
-
- ❌ Criar em `docs/robots/` — pasta incorreta, usar
|
|
47
|
+
- ❌ Criar em `docs/robots/` — pasta incorreta, usar `$ENG_DOCS/robots/`
|
|
48
48
|
- ❌ Omitir a seção de **Limitações Conhecidas** — é fundamental para evitar que outros devs tentem implementar algo impossível
|
|
49
49
|
- ❌ Omitir o **Histórico de Bugs** — descobertas custam tempo; documentar evita retrabalho
|
|
50
50
|
|
|
@@ -61,4 +61,4 @@ Nenhuma — todo robô deve ter sua documentação, independente do tamanho ou c
|
|
|
61
61
|
|
|
62
62
|
## Referências
|
|
63
63
|
|
|
64
|
-
-
|
|
64
|
+
- `$ENG_DOCS/robots/{robot-tag}-robot.md` — referência de implementação completa
|
|
@@ -9,7 +9,8 @@ O Jarvis não assume Jira, GitLab ou Slack. Leia `$IDE/ENV.md` e despache pelo a
|
|
|
9
9
|
| Variável | Vendors | Adapter |
|
|
10
10
|
|----------|---------|---------|
|
|
11
11
|
| `TASK_MANAGER` | jira, linear, github, asana | `jarvis tasks comment` |
|
|
12
|
-
| `VERSION_CONTROL` | gitlab, github, bitbucket | `jarvis vcs create-merge` / `create-issue`
|
|
12
|
+
| `VERSION_CONTROL` | gitlab, github, bitbucket | `jarvis vcs create-merge` / `create-issue` |
|
|
13
|
+
| `CENTRAL_DOCS_STORAGE` | filesystem (s3 e gdrive no futuro) | `jarvis docs fetch` / `sync` / `publish` |
|
|
13
14
|
| `MESSAGE_COMUNICATOR` | slack, discord, teams | runtime `communicator/` |
|
|
14
15
|
|
|
15
16
|
`{TASK_MANAGER_KEY}` = id da tarefa. Com board, é o id do card. Sem board, é o número de controle do usuário.
|
|
@@ -36,7 +37,7 @@ Com board definido, se o key não veio nos argumentos, perguntar o id do card na
|
|
|
36
37
|
|
|
37
38
|
Só se `TASK_MANAGER` estiver preenchido com um vendor. Freelance → pular. Falha no comentário não bloqueia o workflow.
|
|
38
39
|
|
|
39
|
-
Comentar **somente** em eventos relevantes: (a) **desvio do plano** (mudança de direção, fase bloqueada), (b) **auditoria do Advisor** (as questões do `architecture.md` com as opções junto da escolha), (c) **bloqueio** com valor de rastreabilidade
|
|
40
|
+
Comentar **somente** em eventos relevantes: (a) **desvio do plano** (mudança de direção, fase bloqueada), (b) **auditoria do Advisor** (as questões do `architecture.md` com as opções junto da escolha), (c) **bloqueio** com valor de rastreabilidade, (d) **link do MR**, uma única vez, (e) o **`gate-checklist.md` do `eng.pre-pr`**, só se o usuário responder Sim. **Nunca** comentar início ou fim de etapa ("Iniciando…", "concluído…"): o acompanhamento fica no fluxo e no PR.
|
|
40
41
|
|
|
41
42
|
```
|
|
42
43
|
/eng-global-task-comment {TASK_MANAGER_KEY} {mensagem}
|
|
@@ -188,7 +188,7 @@ Quando o robô interage com interface visual (browser headless), usar a especial
|
|
|
188
188
|
|
|
189
189
|
Seguir `$IDE/rules/engineering/eng.docs-scraping-rules.md` sem exceções.
|
|
190
190
|
|
|
191
|
-
Criar ou atualizar
|
|
191
|
+
Criar ou atualizar `$ENG_DOCS/robots/{robot-tag}-robot.md` com:
|
|
192
192
|
- Visão Geral (o que coleta e o que **não** coleta)
|
|
193
193
|
- Fluxo de Execução (sequência de passos com URLs)
|
|
194
194
|
- Fontes de Dados e Campos Extraídos
|
|
@@ -22,7 +22,6 @@ IMPORTANTE: Para que você funcione como o previsto e tenha todas as informaçõ
|
|
|
22
22
|
- `PROD_FLOWS`
|
|
23
23
|
- `PROD_TEMPLATES`
|
|
24
24
|
- `PROD_DOCS`
|
|
25
|
-
- `CENTRAL_DOCS_REPO`
|
|
26
25
|
- **Se o ENV.md não existir ou estiver incompleto**, o agente deve:
|
|
27
26
|
1. Interromper a execução do comando solicitado
|
|
28
27
|
2. Informar ao usuário que não podemos avançar sem o ENV.md. Mensagem abaixo.
|
|
@@ -40,21 +39,20 @@ PROD_FOLDER_NAME=product
|
|
|
40
39
|
PROD_RULES=$RULES_FOLDER/$PROD_FOLDER_NAME
|
|
41
40
|
PROD_FLOWS=.$IDE/$FLOWS_FOLDER
|
|
42
41
|
PROD_TEMPLATES=.$IDE/$TEMPLATES_FOLDER/$PROD_FOLDER_NAME
|
|
43
|
-
PROD_DOCS=$DOCS_FOLDER/$PROD_FOLDER_NAME
|
|
44
|
-
CENTRAL_DOCS_REPO=[USER:LOCAL_CENTRAL_DOCS_REPO]
|
|
42
|
+
PROD_DOCS=$DOCS_FOLDER/jarvis/$PROD_FOLDER_NAME
|
|
45
43
|
```
|
|
46
44
|
|
|
47
45
|
Abaixo, segue uma descrição de algumas das variáveis mais importantes para as rotinas de produto:
|
|
48
46
|
|
|
49
47
|
- $DOCS_FOLDER é o nome da pasta principal de documentações do projeto. Ela fica localizada na raiz do projeto.
|
|
50
|
-
- $PROD_DOCS é
|
|
48
|
+
- $PROD_DOCS é a pasta dos documentos de produto gerados pelos comandos (PRD, FRD, épicos, histórias, discovery): `$JARVIS_DOCS/product` (`docs/jarvis/product`), nos dois modos de trabalho e fora do versionamento do código. Os de engenharia ficam em `$ENG_DOCS` (`docs/jarvis/engineering`).
|
|
51
49
|
- $PROD_FOLDER_NAME é o nome padrão da pasta de produto que será usada em diversos contextos do projeto.
|
|
52
50
|
- $TEMPLATES_FOLDER é o nome da pasta principal de templates do projeto.
|
|
53
51
|
- $PROD_TEMPLATES é o caminho completo para os templates de produto.
|
|
54
52
|
- $FLOWS_FOLDER é o nome da pasta de workflows e comandos utilizados pelos IDEs. (tenha em mente que o nome da pasta pode variar de acordo com a IDE utilizada)
|
|
55
53
|
- $RULES_FOLDER é o nome da pasta principal de regras invioláveis que os agentes devem seguir.
|
|
56
54
|
- $PROD_RULES é o caminho para a pasta que contém as regras de produto. Leia todos os arquivos dentro dessa pasta para entender as regras de produto.
|
|
57
|
-
- $
|
|
55
|
+
- $CENTRAL_DOCS_STORAGE e $CENTRAL_DOCS_LOCATION configuram o Central docs: o registro do que **foi implementado** (um documento por funcionalidade e um por entrega, gerados pelo `/eng.spec-consolidate`), com um índice global. Ligado por padrão (`filesystem`); vazio = desligado. Os documentos de trabalho (PRD, FRD, ARD, RFC) não são publicados lá. Leitura e publicação sempre por `jarvis docs fetch | sync | publish`.
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
## VARIÁVEL `$IDE` - DETECÇÃO AUTOMÁTICA DA PASTA DA IDE
|
|
@@ -116,7 +114,8 @@ Em itens e especificações, utilizamos status para identificar quais etapas do
|
|
|
116
114
|
- backlog: Item já foi estudado, sabemos o que fazer e está aguardando priorização de implementação.
|
|
117
115
|
- in_progress: Item que está em desenvolvimento.
|
|
118
116
|
- in_production: Item que está em produção.
|
|
119
|
-
- cancelled: Item
|
|
117
|
+
- cancelled: Item cancelado antes de ser entregue.
|
|
118
|
+
- deprecated: Item que esteve em produção e foi descontinuado.
|
|
120
119
|
|
|
121
120
|
|
|
122
121
|
## Padrão de nomes e pastas
|