jarvis-ai-framework 5.2.0 → 5.6.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.md +1 -0
- package/README.md +14 -1
- package/bin/commands/init.js +189 -2
- package/bin/commands/tokens.js +9 -3
- package/bin/lib/cli/program.js +18 -2
- package/bin/lib/config/constants.js +5 -0
- package/bin/lib/core/docs-migrate.js +259 -0
- package/bin/lib/core/env-upgrade.js +97 -0
- package/bin/lib/core/profile-filter.js +15 -3
- package/bin/lib/core/stale-skills.js +63 -0
- package/bin/lib/core/sync-engine.js +5 -3
- package/bin/lib/core/token-report.js +1 -0
- package/bin/lib/core/workspace-mode.js +57 -0
- package/bin/lib/docs/storage/build-index.js +2 -1
- package/bin/lib/vcs/api.js +13 -1
- package/bin/lib/vcs/create-issue.js +17 -3
- package/package.json +2 -2
- package/rules/AGENTS.md +4 -1
- package/rules/artifact-style-rules.md +55 -0
- package/rules/engineering/eng-rules.md +12 -1
- package/rules/engineering/eng.integrations-rules.md +1 -1
- package/rules/product/prod-rules.md +6 -3
- package/rules/product/prod.spec-rules.md +2 -2
- package/rules-on-demand/engineering/eng.plan-rules.md +1 -1
- package/rules-on-demand/engineering/eng.pr-rules.md +6 -5
- package/rules-on-demand/engineering/eng.pre-pr-rules.md +2 -2
- package/rules-on-demand/engineering/eng.start-rules.md +2 -2
- package/rules-on-demand/engineering/eng.transitions-rules.md +67 -0
- package/rules-on-demand/engineering/eng.work-rules.md +9 -3
- package/skills/eng-global-pr/SKILL.md +3 -1
- package/skills/eng-global-task-comment/SKILL.md +2 -0
- package/skills/jarvis-create-specialization/SKILL.md +1 -1
- package/skills/jarvis-docs-central/SKILL.md +1 -1
- package/skills/jarvis-init/SKILL.md +64 -23
- package/skills/jarvis-init/assets/passo-adicionar-stacks.md +1 -1
- package/skills/jarvis-init/assets/setup-guide.md +3 -3
- package/skills/jarvis-report-issue/SKILL.md +64 -9
- package/templates/ENV-template.md +17 -3
- package/templates/engineering/architecture-template.md +2 -1
- package/templates/engineering/daily-template.md +1 -1
- package/templates/engineering/plan-template.md +2 -0
- package/templates/product/prod-epic-template.md +1 -1
- package/templates/product/prod-frd-template.md +1 -1
- package/templates/product/prod-issue-template.md +1 -1
- package/templates/product/prod-prd-template.md +1 -1
- package/workflows/engineering/eng.plan.md +3 -2
- package/workflows/engineering/eng.pr.md +60 -16
- package/workflows/engineering/eng.pre-pr.md +21 -8
- package/workflows/engineering/eng.spec-consolidate.md +7 -1
- package/workflows/engineering/eng.start.md +11 -3
- package/workflows/engineering/eng.work.md +23 -6
- package/workflows/product/prod.spec.discovery.md +2 -2
- package/workflows/warm-up.md +2 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Upgrade do ENV.md (`jarvis init --upgrade`): acrescenta as chaves do template que faltam.
|
|
2
|
+
// Regra: só acrescenta. Nenhuma chave existente muda, preenchida, vazia ou com lista de opções.
|
|
3
|
+
// Mesmas regras do Upgrade (C) do skill jarvis-init (itens 4, 5, 9, 10 e 11).
|
|
4
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { detectWorkspaceMode } from "./workspace-mode.js";
|
|
6
|
+
|
|
7
|
+
const KEY_LINE = /^([A-Z][A-Z0-9_]*)=(.*)$/;
|
|
8
|
+
|
|
9
|
+
// PROD_MODE nova: o valor vem da AREA (só no upgrade; a criação do ENV continua perguntando).
|
|
10
|
+
const PROD_MODE_BY_AREA = { ENGINEERING: "developer-guided", PRODUCT: "product-led" };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Lê as chaves do ENV na ordem do arquivo, **incluindo as vazias** (o `loadEnv`
|
|
14
|
+
* descarta as vazias, o que esconderia uma chave existente do diff).
|
|
15
|
+
* Linhas comentadas não contam.
|
|
16
|
+
* @returns {Map<string, string>}
|
|
17
|
+
*/
|
|
18
|
+
export function parseEnvEntries(text) {
|
|
19
|
+
const entries = new Map();
|
|
20
|
+
for (const line of text.split("\n")) {
|
|
21
|
+
const m = line.trim().match(KEY_LINE);
|
|
22
|
+
if (m && !entries.has(m[1])) entries.set(m[1], m[2].trim());
|
|
23
|
+
}
|
|
24
|
+
return entries;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isOptionList(value) {
|
|
28
|
+
return /^\[.*\]$/.test(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Bloco "Data Engineering": DATA_* só entram com HUB=DATA. DATA_SPECIALIZATIONS é
|
|
32
|
+
// obrigatória em qualquer HUB (eng.specializations-rules.md) e nunca é excluída.
|
|
33
|
+
function isDataOnlyKey(key) {
|
|
34
|
+
return key.startsWith("DATA_") && key !== "DATA_SPECIALIZATIONS";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Calcula o que o upgrade acrescenta, sem gravar nada.
|
|
39
|
+
* @param {string} envText
|
|
40
|
+
* @param {string} templateText
|
|
41
|
+
* @param {{ workspaceDir?: string }} [options] - `workspaceDir`: pasta que contém o `$IDE/`, para detectar o `WORKSPACE_MODE`
|
|
42
|
+
* @returns {{ added: {key: string, value: string}[], pending: string[], warnings: string[] }}
|
|
43
|
+
* `pending` e `warnings` trazem só nomes de chave, nunca valores do ENV.
|
|
44
|
+
*/
|
|
45
|
+
export function planEnvUpgrade(envText, templateText, { workspaceDir } = {}) {
|
|
46
|
+
const env = parseEnvEntries(envText);
|
|
47
|
+
const template = parseEnvEntries(templateText);
|
|
48
|
+
const hub = (env.get("HUB") || "").toUpperCase();
|
|
49
|
+
const area = (env.get("AREA") || "").toUpperCase();
|
|
50
|
+
|
|
51
|
+
const added = [];
|
|
52
|
+
const pending = [];
|
|
53
|
+
const warnings = [];
|
|
54
|
+
|
|
55
|
+
for (const [key, templateValue] of template) {
|
|
56
|
+
if (env.has(key)) continue;
|
|
57
|
+
if (hub !== "DATA" && isDataOnlyKey(key)) continue;
|
|
58
|
+
|
|
59
|
+
let value = isOptionList(templateValue) ? "" : templateValue;
|
|
60
|
+
if (key === "PROD_MODE") {
|
|
61
|
+
value = PROD_MODE_BY_AREA[area] || "";
|
|
62
|
+
if (!value) pending.push("PROD_MODE");
|
|
63
|
+
}
|
|
64
|
+
if (key === "WORKSPACE_MODE" && workspaceDir) {
|
|
65
|
+
const repos = (env.get("WORKSPACE_REPOS") || "")
|
|
66
|
+
.split(",")
|
|
67
|
+
.map((r) => r.trim())
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
const detected = detectWorkspaceMode(workspaceDir, { repos });
|
|
70
|
+
value = detected.mode;
|
|
71
|
+
if (detected.warning) warnings.push("WORKSPACE_MODE");
|
|
72
|
+
}
|
|
73
|
+
added.push({ key, value });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (env.has("PROD_MODE") && !env.get("PROD_MODE")) pending.push("PROD_MODE");
|
|
77
|
+
|
|
78
|
+
if (env.get("CENTRAL_DOCS_REPO") && !env.has("CENTRAL_DOCS_STORAGE")) {
|
|
79
|
+
warnings.push("CENTRAL_DOCS_REPO");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { added, pending, warnings };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Grava as chaves novas no fim do ENV, num bloco datado. Nenhuma linha existente muda.
|
|
87
|
+
* `overrides` troca o valor de chaves novas (ex.: recusa em mover os discoveries).
|
|
88
|
+
* @returns {boolean} true se gravou
|
|
89
|
+
*/
|
|
90
|
+
export function applyEnvUpgrade(envPath, plan, today, overrides = {}) {
|
|
91
|
+
if (plan.added.length === 0) return false;
|
|
92
|
+
const text = readFileSync(envPath, "utf-8");
|
|
93
|
+
const lines = plan.added.map(({ key, value }) => `${key}=${overrides[key] ?? value}`);
|
|
94
|
+
const sep = text.endsWith("\n") ? "" : "\n";
|
|
95
|
+
writeFileSync(envPath, `${text}${sep}\n# --- Upgrade ${today} ---\n${lines.join("\n")}\n`);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* `rules/AGENTS.md`: ao mudar uma, mude as três.
|
|
8
8
|
*
|
|
9
9
|
* Regras:
|
|
10
|
-
* - Os
|
|
10
|
+
* - Os eixos (HUB, POSITION, AREA, SQUAD e o opcional ARTIFACT_STYLE) são combinados com AND.
|
|
11
|
+
* - ARTIFACT_STYLE (5º campo, opcional): ausente no bloco = `all`; perfil sem valor =
|
|
12
|
+
* `ai-generated`. Sem coringa: FULLCYCLE e GENERALIST não o satisfazem (é escolha da pessoa).
|
|
11
13
|
* - `all` casa qualquer valor; listas são separadas por vírgula.
|
|
12
14
|
* - FULLCYCLE (HUB) satisfaz qualquer HUB.
|
|
13
15
|
* - GENERALIST (POSITION) satisfaz qualquer POSITION e qualquer AREA.
|
|
@@ -20,8 +22,11 @@
|
|
|
20
22
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
23
|
import { join, relative, sep } from "node:path";
|
|
22
24
|
|
|
25
|
+
// SQUAD para no próximo `|`: o 5º campo (ARTIFACT_STYLE) é opcional.
|
|
23
26
|
const APPLIES_TO_RE =
|
|
24
|
-
/> \*\*Applies to:\*\* HUB: ([^|]+)\| POSITION: ([^|]+)\| AREA: ([^|]+)\| SQUAD: (
|
|
27
|
+
/> \*\*Applies to:\*\* HUB: ([^|]+)\| POSITION: ([^|]+)\| AREA: ([^|]+)\| SQUAD: ([^|\n]+)(?:\| ARTIFACT_STYLE: ([^|\n]+))?/;
|
|
28
|
+
|
|
29
|
+
const DEFAULT_ARTIFACT_STYLE = "ai-generated";
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
32
|
* @typedef {Object} AppliesTo
|
|
@@ -29,6 +34,7 @@ const APPLIES_TO_RE =
|
|
|
29
34
|
* @property {string} POSITION
|
|
30
35
|
* @property {string} AREA
|
|
31
36
|
* @property {string} SQUAD
|
|
37
|
+
* @property {string} ARTIFACT_STYLE - `all` quando o bloco não traz o campo
|
|
32
38
|
*/
|
|
33
39
|
|
|
34
40
|
/**
|
|
@@ -37,6 +43,7 @@ const APPLIES_TO_RE =
|
|
|
37
43
|
* @property {string} POSITION
|
|
38
44
|
* @property {string} AREA
|
|
39
45
|
* @property {string} SQUAD
|
|
46
|
+
* @property {string} [ARTIFACT_STYLE] - vazio = `ai-generated`
|
|
40
47
|
*/
|
|
41
48
|
|
|
42
49
|
/**
|
|
@@ -52,6 +59,7 @@ export function parseAppliesTo(content) {
|
|
|
52
59
|
POSITION: match[2].trim(),
|
|
53
60
|
AREA: match[3].trim(),
|
|
54
61
|
SQUAD: match[4].trim(),
|
|
62
|
+
ARTIFACT_STYLE: (match[5] || "all").trim(),
|
|
55
63
|
};
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -76,7 +84,11 @@ export function matchesProfile(appliesTo, profile) {
|
|
|
76
84
|
const position = generalist || axisMatches(appliesTo.POSITION, profile.POSITION);
|
|
77
85
|
const area = generalist || axisMatches(appliesTo.AREA, profile.AREA);
|
|
78
86
|
const squad = axisMatches(appliesTo.SQUAD, profile.SQUAD);
|
|
79
|
-
|
|
87
|
+
const style = axisMatches(
|
|
88
|
+
appliesTo.ARTIFACT_STYLE || "all",
|
|
89
|
+
profile.ARTIFACT_STYLE || DEFAULT_ARTIFACT_STYLE
|
|
90
|
+
);
|
|
91
|
+
return hub && position && area && squad && style;
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
function walkMarkdown(dir) {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Skills obsoletos (`jarvis init --upgrade`): skills instalados na pasta da IDE que não existem
|
|
2
|
+
// mais no framework (renomeados ou removidos). O upgrade só LISTA e, com confirmação, remove.
|
|
3
|
+
// Nunca marca como obsoleto um skill registrado em alguma lista `*_SPECIALIZATIONS` do ENV.md
|
|
4
|
+
// (especializações criadas pelo usuário com o jarvis-create-specialization).
|
|
5
|
+
// Só lê nomes de pasta: nunca lê nem imprime o conteúdo dos skills.
|
|
6
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
const SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Nomes registrados nas listas `*_SPECIALIZATIONS` do ENV (aceita vírgulas, colchetes, aspas e espaços).
|
|
13
|
+
* @param {Map<string, string>} entries - `parseEnvEntries(envText)`
|
|
14
|
+
* @returns {Set<string>}
|
|
15
|
+
*/
|
|
16
|
+
export function registeredSpecializations(entries) {
|
|
17
|
+
const names = new Set();
|
|
18
|
+
for (const [key, value] of entries) {
|
|
19
|
+
if (!key.endsWith("_SPECIALIZATIONS")) continue;
|
|
20
|
+
for (const item of value.replace(/[[\]"']/g, "").split(",")) {
|
|
21
|
+
const name = item.trim();
|
|
22
|
+
if (name) names.add(name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return names;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{ ideSkillsDir: string, frameworkSkillsDir: string, registered?: Set<string> }} p
|
|
30
|
+
* @returns {string[]} nomes (pastas) de skills instalados que o framework não tem mais
|
|
31
|
+
*/
|
|
32
|
+
export function findStaleSkills({ ideSkillsDir, frameworkSkillsDir, registered = new Set() }) {
|
|
33
|
+
if (!existsSync(ideSkillsDir)) return [];
|
|
34
|
+
return readdirSync(ideSkillsDir, { withFileTypes: true })
|
|
35
|
+
.filter((e) => e.isDirectory() && SKILL_NAME.test(e.name))
|
|
36
|
+
.map((e) => e.name)
|
|
37
|
+
.filter((name) => existsSync(join(ideSkillsDir, name, "SKILL.md")))
|
|
38
|
+
.filter((name) => !existsSync(join(frameworkSkillsDir, name)))
|
|
39
|
+
.filter((name) => !registered.has(name))
|
|
40
|
+
.sort();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove as pastas listadas, uma a uma (só nomes já validados por `findStaleSkills`).
|
|
45
|
+
* @returns {{ removed: string[], errors: string[] }}
|
|
46
|
+
*/
|
|
47
|
+
export function removeStaleSkills(ideSkillsDir, names) {
|
|
48
|
+
const removed = [];
|
|
49
|
+
const errors = [];
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
if (!SKILL_NAME.test(name)) {
|
|
52
|
+
errors.push(`${name}: nome inválido`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
rmSync(join(ideSkillsDir, name), { recursive: true, force: true });
|
|
57
|
+
removed.push(name);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
errors.push(`${name}: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { removed, errors };
|
|
63
|
+
}
|
|
@@ -149,7 +149,7 @@ export function syncAssets(targetDir, ide, opts = {}) {
|
|
|
149
149
|
|
|
150
150
|
// RTK: detectar e reconciliar RTK_ENABLED + rtk-rules.md
|
|
151
151
|
if (!opts.dryRun) {
|
|
152
|
-
const rtkResult = reconcileRtk(ideDir, targetDir, ide);
|
|
152
|
+
const rtkResult = reconcileRtk(ideDir, targetDir, ide, { keepEnvValues: opts.keepEnvValues });
|
|
153
153
|
if (rtkResult) result.copied.push(rtkResult);
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -191,9 +191,11 @@ export function syncAssets(targetDir, ide, opts = {}) {
|
|
|
191
191
|
* Detecta RTK no PATH e reconcilia RTK_ENABLED no ENV.md + rtk-rules.md na IDE.
|
|
192
192
|
* - RTK encontrado → RTK_ENABLED=true no ENV.md, mantém rule
|
|
193
193
|
* - RTK não encontrado → remove rtk-rules.md da IDE, mantém RTK_ENABLED=false
|
|
194
|
+
* - `keepEnvValues` (jarvis init --upgrade): não troca RTK_ENABLED=false por true, porque o
|
|
195
|
+
* upgrade só acrescenta chaves novas; acrescentar RTK_ENABLED quando ela não existe continua valendo
|
|
194
196
|
* @returns {string|null} mensagem de log ou null se nenhuma ação
|
|
195
197
|
*/
|
|
196
|
-
function reconcileRtk(ideDir, targetDir, ide) {
|
|
198
|
+
function reconcileRtk(ideDir, targetDir, ide, { keepEnvValues = false } = {}) {
|
|
197
199
|
const rtkRulePath = join(ideDir, "rules", "rtk-rules.md");
|
|
198
200
|
const envPath = join(ideDir, "ENV.md");
|
|
199
201
|
const hasRtk = (() => {
|
|
@@ -209,7 +211,7 @@ function reconcileRtk(ideDir, targetDir, ide) {
|
|
|
209
211
|
// Ativar RTK_ENABLED no ENV.md se existir e estiver false
|
|
210
212
|
if (existsSync(envPath)) {
|
|
211
213
|
let env = readFileSync(envPath, "utf-8");
|
|
212
|
-
if (env.includes("RTK_ENABLED=false")) {
|
|
214
|
+
if (env.includes("RTK_ENABLED=false") && !keepEnvValues) {
|
|
213
215
|
env = env.replace("RTK_ENABLED=false", "RTK_ENABLED=true");
|
|
214
216
|
writeFileSync(envPath, env, "utf-8");
|
|
215
217
|
return "(rtk) detectado — RTK_ENABLED=true no ENV.md";
|
|
@@ -91,6 +91,7 @@ export function buildReport(root, profile, opts = {}) {
|
|
|
91
91
|
POSITION: profile.POSITION,
|
|
92
92
|
AREA: profile.AREA,
|
|
93
93
|
SQUAD: profile.SQUAD,
|
|
94
|
+
ARTIFACT_STYLE: profile.ARTIFACT_STYLE || "ai-generated",
|
|
94
95
|
},
|
|
95
96
|
rtkEnabled,
|
|
96
97
|
estimate: `tokens = bytes ÷ ${BYTES_PER_TOKEN} (estimativa)`,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Modo de trabalho do workspace (WORKSPACE_MODE): decide se a pasta que contém o `$IDE/` é
|
|
2
|
+
// um projeto único ou um workspace. O que define o modo é a RAIZ ser ou não um repositório,
|
|
3
|
+
// e não quantos repositórios existem dentro dela:
|
|
4
|
+
// - raiz com `.git` → "project" (o git já delimita o código)
|
|
5
|
+
// - raiz sem `.git` e 1+ subpastas com `.git` → "workspace"
|
|
6
|
+
// - raiz sem `.git` e nenhuma subpasta com `.git` → "project" (padrão)
|
|
7
|
+
// Caso ambíguo (raiz com `.git` e subpastas também com `.git`, como submódulos): "project" + aviso.
|
|
8
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
export const WORKSPACE_MODES = ["workspace", "project"];
|
|
12
|
+
|
|
13
|
+
/** Subpastas diretas com `.git` (arquivo ou pasta). Ignora ocultas e `node_modules`. */
|
|
14
|
+
function listGitSubdirs(dir) {
|
|
15
|
+
let entries;
|
|
16
|
+
try {
|
|
17
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return entries
|
|
22
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules")
|
|
23
|
+
.filter((e) => existsSync(join(dir, e.name, ".git")))
|
|
24
|
+
.map((e) => e.name)
|
|
25
|
+
.sort();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} workspaceDir - pasta que contém o `$IDE/`
|
|
30
|
+
* @param {{ repos?: string[] }} [options] - `repos`: allowlist (WORKSPACE_REPOS); vazia = todas
|
|
31
|
+
* @returns {{ mode: "workspace"|"project", repos: string[], warning: string|null }}
|
|
32
|
+
*/
|
|
33
|
+
export function detectWorkspaceMode(workspaceDir, { repos = [] } = {}) {
|
|
34
|
+
const rootIsRepo = existsSync(join(workspaceDir, ".git"));
|
|
35
|
+
const allow = new Set(repos);
|
|
36
|
+
const found = listGitSubdirs(workspaceDir).filter((n) => allow.size === 0 || allow.has(n));
|
|
37
|
+
|
|
38
|
+
if (rootIsRepo) {
|
|
39
|
+
return {
|
|
40
|
+
mode: "project",
|
|
41
|
+
repos: [],
|
|
42
|
+
warning: found.length
|
|
43
|
+
? "a raiz é um repositório e há subpastas com .git (submódulos ou repositórios aninhados): tratada como projeto único"
|
|
44
|
+
: null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (found.length >= 1) return { mode: "workspace", repos: found, warning: null };
|
|
48
|
+
return { mode: "project", repos: [], warning: null };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Lê o valor do ENV: só `workspace` ou `project` (sem diferenciar maiúsculas); outro valor = null. */
|
|
52
|
+
export function normalizeWorkspaceMode(value) {
|
|
53
|
+
const v = String(value || "")
|
|
54
|
+
.trim()
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
return WORKSPACE_MODES.includes(v) ? v : null;
|
|
57
|
+
}
|
|
@@ -53,7 +53,8 @@ export function renderIndex(root, squad) {
|
|
|
53
53
|
const areaDir = join(root, squad, area);
|
|
54
54
|
const files = walkFiles(areaDir).filter((f) => {
|
|
55
55
|
const name = f.split(sep).pop();
|
|
56
|
-
|
|
56
|
+
// {nome}.public.md (versão humanizada, ARTIFACT_STYLE=human-generated) não entra no índice
|
|
57
|
+
return name.startsWith(prefix) && name.endsWith(".md") && !name.endsWith(".public.md");
|
|
57
58
|
});
|
|
58
59
|
out.push(`## ${area}`, "", `> ${title}`, "");
|
|
59
60
|
if (files.length === 0) {
|
package/bin/lib/vcs/api.js
CHANGED
|
@@ -108,6 +108,14 @@ export async function createMergeRequest(repoUrl, opts) {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// Tipos de issue do Bitbucket (campo `kind`; ele não tem labels livres).
|
|
112
|
+
const BITBUCKET_KINDS = ["bug", "enhancement", "proposal", "task"];
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} repoUrl
|
|
116
|
+
* @param {{ title: string, body: string, labels?: string[] }} opts - `labels` padrão: ["bug"].
|
|
117
|
+
* No Bitbucket, a primeira label vira o `kind` quando é um tipo válido; senão, `bug`.
|
|
118
|
+
*/
|
|
111
119
|
export async function createIssue(repoUrl, opts) {
|
|
112
120
|
const { title, body, labels = ["bug"] } = opts;
|
|
113
121
|
const repo = parseRepoUrl(repoUrl);
|
|
@@ -149,7 +157,11 @@ export async function createIssue(repoUrl, opts) {
|
|
|
149
157
|
{
|
|
150
158
|
method: "POST",
|
|
151
159
|
headers,
|
|
152
|
-
body: {
|
|
160
|
+
body: {
|
|
161
|
+
title,
|
|
162
|
+
content: { raw: body },
|
|
163
|
+
kind: BITBUCKET_KINDS.includes(labels[0]) ? labels[0] : "bug",
|
|
164
|
+
},
|
|
153
165
|
}
|
|
154
166
|
);
|
|
155
167
|
if (!ok || !json?.links?.html?.href) {
|
|
@@ -9,11 +9,25 @@ import { createIssue, parseRepoUrl } from "./api.js";
|
|
|
9
9
|
* @param {string} options.repo - URL ou `org/repo` do repositório
|
|
10
10
|
* @param {string} options.title - Título da issue
|
|
11
11
|
* @param {string} options.bodyFile - Path de um arquivo com o corpo em markdown
|
|
12
|
+
* @param {string} [options.labels] - Labels separadas por vírgula (padrão: bug)
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* "enhancement, documentation" → ["enhancement", "documentation"]; vazio → undefined (padrão: bug).
|
|
16
|
+
* @param {string} [raw]
|
|
17
|
+
* @returns {string[] | undefined}
|
|
18
|
+
*/
|
|
19
|
+
export function parseLabels(raw) {
|
|
20
|
+
const list = String(raw || "")
|
|
21
|
+
.split(",")
|
|
22
|
+
.map((l) => l.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
return list.length ? list : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runCreateIssue({ repo, title, bodyFile, labels }) {
|
|
14
28
|
if (!title || !bodyFile || !repo) {
|
|
15
29
|
console.error(
|
|
16
|
-
"Uso: jarvis vcs create-issue --repo <url-ou-path> --title <titulo> --body-file <path>"
|
|
30
|
+
"Uso: jarvis vcs create-issue --repo <url-ou-path> --title <titulo> --body-file <path> [--label <labels>]"
|
|
17
31
|
);
|
|
18
32
|
process.exit(1);
|
|
19
33
|
}
|
|
@@ -33,7 +47,7 @@ export async function runCreateIssue({ repo, title, bodyFile }) {
|
|
|
33
47
|
const body = existsSync(bodyFile) ? readFileSync(bodyFile, "utf-8") : "";
|
|
34
48
|
|
|
35
49
|
try {
|
|
36
|
-
const result = await createIssue(repoUrl, { title, body });
|
|
50
|
+
const result = await createIssue(repoUrl, { title, body, labels: parseLabels(labels) });
|
|
37
51
|
parseRepoUrl(repoUrl);
|
|
38
52
|
console.log(`✅ Issue aberto: ${result.url}`);
|
|
39
53
|
console.log(JSON.stringify(result));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jarvis-ai-framework",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.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 test/docs-storage.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 test/env-upgrade.test.js test/docs-migrate.test.js test/init-upgrade.test.js test/workspace-mode.test.js test/stale-skills.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
|
@@ -75,6 +75,8 @@ Cada arquivo de rule declara para quais perfis se aplica via bloco `applies_to`,
|
|
|
75
75
|
> **Applies to:** HUB: {valor ou all} | POSITION: {valor ou all} | AREA: {valor ou all} | SQUAD: {valor ou all}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
Campo opcional no fim: `| ARTIFACT_STYLE: {ai-generated, human-generated ou all}` (ausente = `all`).
|
|
79
|
+
|
|
78
80
|
**Eixos de filtragem:**
|
|
79
81
|
|
|
80
82
|
| Eixo | Exemplos de valor | `all` significa |
|
|
@@ -83,13 +85,14 @@ Cada arquivo de rule declara para quais perfis se aplica via bloco `applies_to`,
|
|
|
83
85
|
| POSITION | TECH LEAD, PM, QA-ENGINEER, SENIOR, GENERALIST | qualquer cargo |
|
|
84
86
|
| AREA | ENGINEERING, PRODUCT | qualquer área |
|
|
85
87
|
| SQUAD | CORE | qualquer squad |
|
|
88
|
+
| ARTIFACT_STYLE (opcional) | ai-generated, human-generated | qualquer estilo (ENV vazio = `ai-generated`; sem coringa) |
|
|
86
89
|
|
|
87
90
|
**Regras:**
|
|
88
91
|
- Uma rule é aplicada se **todos** os eixos forem satisfeitos (AND, não OR)
|
|
89
92
|
- Arquivos sem bloco `applies_to` são considerados **universais** — copiados para qualquer perfil
|
|
90
93
|
- `rules/AGENTS.md` nunca é filtrado — sempre copiado
|
|
91
94
|
|
|
92
|
-
**Quem faz a filtragem:** o skill `/jarvis-init` (Passo
|
|
95
|
+
**Quem faz a filtragem:** o skill `/jarvis-init` (Passo 8 — Profile-Aware Rules Sync). Ao criar, atualizar ou fazer upgrade do ENV.md, copia para `$IDE/rules/` apenas as rules que batem com o perfil (HUB + POSITION + AREA + SQUAD + ARTIFACT_STYLE) e deleta as que não batem mais.
|
|
93
96
|
|
|
94
97
|
**Ao criar uma nova rule**, definir o bloco `applies_to` é obrigatório. Sem ele, a rule é tratada como universal — o que pode ser indesejado para rules domain-specific.
|
|
95
98
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
> **Applies to:** HUB: all | POSITION: all | AREA: all | SQUAD: all | ARTIFACT_STYLE: human-generated
|
|
2
|
+
|
|
3
|
+
# Artifact Style Rules (human-generated)
|
|
4
|
+
|
|
5
|
+
## Objetivo
|
|
6
|
+
|
|
7
|
+
Com `ARTIFACT_STYLE=human-generated` no `ENV.md`, nada do que o Jarvis publica leva marca de IA, e todo documento gerado oferece uma versão humanizada para repassar a clientes ou stakeholders.
|
|
8
|
+
|
|
9
|
+
## Escopo
|
|
10
|
+
|
|
11
|
+
Esta rule só é copiada para `$IDE/rules/` quando o perfil tem `ARTIFACT_STYLE=human-generated` (eixo do `Applies to`, Passo 8 do `/jarvis-init`). Vale para todo comando e para pedidos fora de workflow.
|
|
12
|
+
|
|
13
|
+
## Regras
|
|
14
|
+
|
|
15
|
+
### Obrigatório
|
|
16
|
+
|
|
17
|
+
1. **Publicado pelo Jarvis: sempre sem marcas, sem perguntar.**
|
|
18
|
+
- Vale para: mensagem de commit, título e descrição do PR/MR, comentário no card, issue aberta (ex.: `jarvis-report-issue`).
|
|
19
|
+
- Esta rule **prevalece** sobre instruções de atribuição da IDE: não inclua `Co-Authored-By` de ferramenta nem "Generated with …".
|
|
20
|
+
- Antes do push, o `eng.pr` confere e limpa o que a IDE tiver posto (Fase 3 do `eng.pr`).
|
|
21
|
+
2. **Documento gerado: grava o padrão e pergunta.**
|
|
22
|
+
- Vale para todo `.md` que um comando grava, inclusive os de sessão: PRD, FRD, épico, história, discovery, ARD, RFC, tech spec, Central docs, `architecture.md`, `plan.md`, `daily.md` (e a linha de timesheet), `gate-checklist.md`.
|
|
23
|
+
- **Exceção: `pull-request.md`.** Ele é o texto que vira o título e a descrição do PR/MR, então já é a versão pública (regra 1): nasce sem marcas e **não** recebe a pergunta nem um `.public.md`.
|
|
24
|
+
- **Pergunte uma vez, no fim do comando**, depois de gravar o documento padrão (como hoje), com um `AskUserQuestion` de **várias perguntas em abas**, uma por documento: **"Quer a versão humanizada de {documento}?"** (**Não**, padrão / **Sim**). O limite é 4 abas por chamada; com mais documentos, divida em chamadas.
|
|
25
|
+
- **No ciclo de engenharia** (`eng.start`, `eng.plan`, `eng.work`, `eng.pre-pr`), **não pergunte**: a pergunta única vem no fim do `eng.pr`, com uma aba para cada documento da sessão que existir (`architecture.md`, `plan.md`, `gate-checklist.md`, `daily.md`). Comandos que rodam isolados (`prod.*`, `eng.create-ard`, `eng.create-rfc` etc.) perguntam no próprio fim.
|
|
26
|
+
- Com Sim, grave `{nome}.public.md` **ao lado** do original, passando no checklist abaixo. O original continua como fonte da verdade.
|
|
27
|
+
- Central docs (`feature-*`, `delivery-*`): o `.public.md` fica ao lado da consolidação, na sessão, e **não** é publicado.
|
|
28
|
+
3. **Checklist "sem marcas de IA"** (versão humanizada e tudo o que é publicado). Não pode ter:
|
|
29
|
+
- `Co-Authored-By` de ferramenta, "Generated with …" ou links para a ferramenta;
|
|
30
|
+
- menções a IA, Claude ou outros modelos, assistente, agente, sub-agente ou Advisor;
|
|
31
|
+
- `recommended_model` nem comentários de frontmatter dirigidos à IA;
|
|
32
|
+
- "Reportado por: IA" ou qualquer autoria atribuída à ferramenta.
|
|
33
|
+
4. **Estilo da versão humanizada:** texto corrido de pessoa para pessoa. Parágrafos onde cabe parágrafo; lista só quando há itens; sem emojis de status em excesso; sem fórmulas como "Em resumo", "Vale ressaltar". Mantenha o conteúdo, as decisões e os links úteis ao leitor (issue, PR, card).
|
|
34
|
+
|
|
35
|
+
### Proibido
|
|
36
|
+
|
|
37
|
+
- Alterar o documento original para gerar a versão humanizada.
|
|
38
|
+
- Publicar commit, PR, comentário ou issue com qualquer item do checklist.
|
|
39
|
+
- Reescrever commits já enviados ao remoto.
|
|
40
|
+
- Contar `{nome}.public.md` na sequência de IDs (`prd-{id}`, `discovery-{id}`…) ou incluí-lo em índices.
|
|
41
|
+
|
|
42
|
+
### Recomendado
|
|
43
|
+
|
|
44
|
+
- Com vários documentos gerados, as abas do `AskUserQuestion` (uma por documento) em vez de perguntas em sequência.
|
|
45
|
+
|
|
46
|
+
## Exceções
|
|
47
|
+
|
|
48
|
+
- Com `ARTIFACT_STYLE=ai-generated` (ou vazio), esta rule não existe na sessão e nada muda.
|
|
49
|
+
|
|
50
|
+
## Referências
|
|
51
|
+
|
|
52
|
+
- `templates/ENV-template.md` (`ARTIFACT_STYLE`)
|
|
53
|
+
- `skills/jarvis-init/SKILL.md` (Pergunta 9 e Passo 8)
|
|
54
|
+
- `workflows/engineering/eng.pr.md` (conferência antes do push)
|
|
55
|
+
- `rules/AGENTS.md` (eixo `ARTIFACT_STYLE` do `Applies to`)
|
|
@@ -28,6 +28,7 @@ env_file: "@/ENV.md"
|
|
|
28
28
|
```
|
|
29
29
|
- **Listas de especializações (presentes, podem estar vazias)**: `BACKEND_SPECIALIZATIONS`, `FRONTEND_SPECIALIZATIONS`, `QA_SPECIALIZATIONS`, `DATA_SPECIALIZATIONS`, `AUTOMATION_SPECIALIZATIONS` e `PLATFORM_SPECIALIZATIONS` devem **existir** no `ENV.md`. Lista **vazia** é válida (só o skill base). Variável **ausente** interrompe o comando daquela área e orienta o Upgrade do `/jarvis-init` — mas só quando um comando daquela área específica é executado (igual já valia pra backend/frontend; não bloqueia comando de uma área não tocada). Detalhes e mensagens em `eng.specializations-rules.md`.
|
|
30
30
|
- **Skills sem área**: skill instalado sem `metadata.area` reconhecida bloqueia o comando, e skill pedido que não existe gera aviso. Detalhes e mensagens em `eng.skills-rules.md`.
|
|
31
|
+
- **Modo de trabalho (`WORKSPACE_MODE`, não bloqueia)**: `workspace` (a raiz que contém `$IDE/` não é um repositório e há 1 ou mais subpastas com `.git/`) ou `project` (a raiz tem `.git`). Ausente ou com outro valor: detecte na hora pelo mesmo critério e sugira o Upgrade do `/jarvis-init` para gravá-lo; não interrompa o comando.
|
|
31
32
|
|
|
32
33
|
- **🔧 VARIÁVEL `$IDE` - DETECÇÃO AUTOMÁTICA DA PASTA DA IDE**
|
|
33
34
|
- A variável `$IDE` representa a pasta da IDE que o usuário está utilizando.
|
|
@@ -37,7 +38,9 @@ env_file: "@/ENV.md"
|
|
|
37
38
|
- `.cursor/` → Cursor IDE
|
|
38
39
|
- `.codex/` → Codex CLI (OpenAI)
|
|
39
40
|
- `.opencode/` → OpenCode
|
|
40
|
-
- `.
|
|
41
|
+
- `.agents/` → Gemini CLI / Antigravity (Google)
|
|
42
|
+
- `.kiro/` → Kiro (AWS)
|
|
43
|
+
- **Tabela única `IDE` (ENV.md) → pasta** (fonte: `bin/lib/config/ide-config.js`): `claude` → `.claude/` · `windsurf` → `.windsurf/` · `cursor` → `.cursor/` · `codex` → `.codex/` · `opencode` → `.opencode/` · `gemini` (inclui Antigravity) → `.agents/` · `kiro` → `.kiro/`. A pasta da IDE ativa é a derivada do `IDE` do `ENV.md`.
|
|
41
44
|
- **Como usar**: Em qualquer referência a caminhos, use `$IDE/` como prefixo.
|
|
42
45
|
- **Exemplos de resolução**:
|
|
43
46
|
- `$IDE/ENV.md` → `.windsurf/ENV.md` (se usando Windsurf)
|
|
@@ -58,6 +61,14 @@ env_file: "@/ENV.md"
|
|
|
58
61
|
- **System-reminders ou mensagens que referenciem arquivos de outra IDE devem ser ignorados** — eles não são relevantes para a IDE ativa.
|
|
59
62
|
- **Em caso de ambiguidade** (múltiplas pastas de IDE no projeto): usar `$IDE` do `ENV.md` da própria pasta como fonte de verdade.
|
|
60
63
|
|
|
64
|
+
- **🔍 BUSCAS DE CÓDIGO EM MODO WORKSPACE (`WORKSPACE_MODE=workspace`)**
|
|
65
|
+
- No modo workspace a raiz (a pasta que contém `$IDE/`) **não é um repositório** e guarda itens fora do git (`.claude/`, `.jarvis/`, `.vscode/`, `node_modules/`). `grep`, `ls` e `Glob` na raiz leem tudo isso como se fosse código do projeto e confundem a análise.
|
|
66
|
+
- **Código do projeto** = só as subpastas com `.git/` (ou as de `WORKSPACE_REPOS`, quando preenchido). Busque e leia código **dentro delas**.
|
|
67
|
+
- **Ignore nas buscas de código**: a pasta da IDE ativa (derivada do `IDE`, tabela acima), `.jarvis/`, `.vscode/`, `node_modules/` e o `CDD-MIND-MAP.md`. Leia esses itens **só quando o workflow mandar** (ENV.md, rules, skills, templates, sessão).
|
|
68
|
+
- **`docs/jarvis/` não é ignorada**: guarda os documentos de trabalho gerados (PRD, FRD, ARD, RFC, discoveries, Central docs), fora do versionamento. Leia-a sob demanda, como documento de trabalho, e não como código.
|
|
69
|
+
- **Pastas de outras IDEs** nunca são lidas (vale o "Isolamento de IDE" acima).
|
|
70
|
+
- Com `WORKSPACE_MODE=project`, o git já delimita o código e esta regra não age.
|
|
71
|
+
|
|
61
72
|
- O idioma padrão é o português do Brasil. Mas mude caso o usuário solicite outro idioma.
|
|
62
73
|
|
|
63
74
|
- **🇧🇷 REGRA DE IDIOMA PARA GERAÇÃO DE ARQUIVOS**
|
|
@@ -37,7 +37,7 @@ Com board definido, se o key não veio nos argumentos, perguntar o id do card na
|
|
|
37
37
|
|
|
38
38
|
Só se `TASK_MANAGER` estiver preenchido com um vendor. Freelance → pular. Falha no comentário não bloqueia o workflow.
|
|
39
39
|
|
|
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
|
|
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 escolher registrar (resumo ou relatório completo). **Nunca** comentar início ou fim de etapa ("Iniciando…", "concluído…"): o acompanhamento fica no fluxo e no PR.
|
|
41
41
|
|
|
42
42
|
```
|
|
43
43
|
/eng-global-task-comment {TASK_MANAGER_KEY} {mensagem}
|
|
@@ -40,12 +40,14 @@ PROD_RULES=$RULES_FOLDER/$PROD_FOLDER_NAME
|
|
|
40
40
|
PROD_FLOWS=.$IDE/$FLOWS_FOLDER
|
|
41
41
|
PROD_TEMPLATES=.$IDE/$TEMPLATES_FOLDER/$PROD_FOLDER_NAME
|
|
42
42
|
PROD_DOCS=$DOCS_FOLDER/jarvis/$PROD_FOLDER_NAME
|
|
43
|
+
DISCOVERIES_DOCS=$DOCS_FOLDER/jarvis/discoveries
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
Abaixo, segue uma descrição de algumas das variáveis mais importantes para as rotinas de produto:
|
|
46
47
|
|
|
47
48
|
- $DOCS_FOLDER é o nome da pasta principal de documentações do projeto. Ela fica localizada na raiz do projeto.
|
|
48
|
-
- $PROD_DOCS é a pasta dos documentos de produto gerados pelos comandos (PRD, FRD, épicos, histórias
|
|
49
|
+
- $PROD_DOCS é a pasta dos documentos de produto gerados pelos comandos (PRD, FRD, épicos, histórias): `$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`).
|
|
50
|
+
- $DISCOVERIES_DOCS é a pasta dos discoveries: `$JARVIS_DOCS/discoveries` (`docs/jarvis/discoveries`), fora de `product/`. ENV sem a variável: use `$PROD_DOCS/discoveries` se essa pasta existir, senão `$JARVIS_DOCS/discoveries`, e avise para rodar `jarvis init --upgrade`.
|
|
49
51
|
- $PROD_FOLDER_NAME é o nome padrão da pasta de produto que será usada em diversos contextos do projeto.
|
|
50
52
|
- $TEMPLATES_FOLDER é o nome da pasta principal de templates do projeto.
|
|
51
53
|
- $PROD_TEMPLATES é o caminho completo para os templates de produto.
|
|
@@ -63,7 +65,8 @@ Abaixo, segue uma descrição de algumas das variáveis mais importantes para as
|
|
|
63
65
|
- `.cursor/` → Cursor IDE
|
|
64
66
|
- `.codex/` → Codex CLI (OpenAI)
|
|
65
67
|
- `.opencode/` → OpenCode
|
|
66
|
-
- `.
|
|
68
|
+
- `.agents/` → Gemini CLI / Antigravity (Google)
|
|
69
|
+
- `.kiro/` → Kiro (AWS)
|
|
67
70
|
- **Como usar**: Em qualquer referência a caminhos, use `$IDE/` como prefixo.
|
|
68
71
|
- **Exemplos de resolução**:
|
|
69
72
|
- `$IDE/ENV.md` → `.windsurf/ENV.md` (se usando Windsurf)
|
|
@@ -126,7 +129,7 @@ Dentro das pastas do projeto, os caminhos de pastas e o nome dos arquivos finais
|
|
|
126
129
|
- Documento de Requisitos Funcionais: `$PROD_DOCS/prd-{id}-{nome-do-pai}/frd-{id}-{nome}.md`
|
|
127
130
|
- Épico: `$PROD_DOCS/prd-{id}-{nome-do-pai}/issues/epic-{id}-{nome}.md`
|
|
128
131
|
- História / tarefa: `$PROD_DOCS/prd-{id}-{nome-do-pai}/issues/{story|task}-{id}-{nome}.md`
|
|
129
|
-
- Discovery (rascunho de uma ideia, anterior ao PRD): `$
|
|
132
|
+
- Discovery (rascunho de uma ideia, anterior ao PRD): `$DISCOVERIES_DOCS/discovery-{id}-{nome}.md`. Usa `status` (`in_review` enquanto o rascunho existe; `cancelled` se o veredito for não seguir) e `verdict` (vazio durante a entrevista, `proceed`, `investigate_more` ou `do_not_proceed`). O veredito é uma recomendação; um PRD, FRD, épico ou issue criado depois prevalece como fonte da verdade.
|
|
130
133
|
|
|
131
134
|
O ID deve ser iterado nos novos arquivos seguindo a sequência existente.
|
|
132
135
|
|
|
@@ -147,14 +147,14 @@ A terminologia inconsistente cria confusão. "Cliente" vs "Usuário" vs "Cliente
|
|
|
147
147
|
|
|
148
148
|
### Princípio: Discovery como insumo
|
|
149
149
|
|
|
150
|
-
**Princípio**: O discovery (`$
|
|
150
|
+
**Princípio**: O discovery (`$DISCOVERIES_DOCS/discovery-{id}-{nome}.md`) é um rascunho de suposições. Ele pode servir de contexto para um PRD, FRD, épico ou issue, mas nunca é obrigatório e nunca é tratado como verdade.
|
|
151
151
|
|
|
152
152
|
**O discovery é opcional**:
|
|
153
153
|
- Sem discovery apontado, siga o fluxo do comando normalmente, sem procurar na pasta `discoveries/` e sem perguntar nada sobre discovery. Ter arquivos nessa pasta não tem relação com o comando.
|
|
154
154
|
- Exemplos: `/prod.spec.prd já sei o que quero` segue o fluxo de sempre; `/prod.spec.prd discovery-002` carrega o discovery.
|
|
155
155
|
|
|
156
156
|
**Como apontar e achar o discovery**:
|
|
157
|
-
- O usuário aponta por caminho, nome ou id (como `discovery-002`); procure em `$
|
|
157
|
+
- O usuário aponta por caminho, nome ou id (como `discovery-002`); procure em `$DISCOVERIES_DOCS/`.
|
|
158
158
|
- Se o arquivo não existir: informe, liste os discoveries da pasta e pergunte qual usar, ou se o usuário prefere seguir sem discovery.
|
|
159
159
|
- Se a pasta `discoveries/` não existir: siga sem discovery e não crie a pasta.
|
|
160
160
|
- Se mais de um arquivo casar com o que o usuário apontou: pergunte qual usar, sem escolher sozinho.
|
|
@@ -261,7 +261,7 @@ Cada fase pode ter uma seção `### Comentários:` para:
|
|
|
261
261
|
- ✅ Detalhar cada tarefa com contexto
|
|
262
262
|
- ✅ Indicar dependências (sequencial/paralelo)
|
|
263
263
|
- ✅ Usar os emojis de status corretamente
|
|
264
|
-
- ✅ Validar o plano com o humano antes de prosseguir
|
|
264
|
+
- ✅ Validar o plano com o humano antes de prosseguir (pergunta "O que fazer com o plano?" de `eng.transitions-rules.md`)
|
|
265
265
|
- ✅ Escrever tudo em português (pt-BR)
|
|
266
266
|
|
|
267
267
|
---
|
|
@@ -271,6 +271,7 @@ O MR deve seguir o template em `$IDE/templates/engineering/PR-template.md`:
|
|
|
271
271
|
- ❌ Commitar arquivos de configuração local
|
|
272
272
|
- ❌ Commitar credenciais ou tokens
|
|
273
273
|
- ❌ Mencionar AI/Claude no PR
|
|
274
|
+
- ❌ Com `ARTIFACT_STYLE=human-generated`: push de commit ou PR com marca de IA ou da IDE (`Co-Authored-By` de ferramenta, "Generated with …") — conferir antes (Fase 3.0 do `eng.pr`)
|
|
274
275
|
- ❌ [!IMPORTANT] PUSH DIRETO PARA `main`/`dev`
|
|
275
276
|
|
|
276
277
|
### ✅ SEMPRE FAÇA
|
|
@@ -322,20 +323,20 @@ Antes de considerar o PR completo:
|
|
|
322
323
|
- [ ] Card do Jira movido para "In Review"
|
|
323
324
|
- [ ] Comentários de code review processados
|
|
324
325
|
- [ ] `pull-request.md` aprovado na sessão (e publicado, se o usuário respondeu Sim)
|
|
325
|
-
- [ ]
|
|
326
|
-
- [ ] `daily.md` gerado ou atualizado e aprovado (
|
|
326
|
+
- [ ] Pergunta "O PR já foi aprovado e mergeado?" no fim (Sim → `/eng.spec-consolidate`)
|
|
327
|
+
- [ ] `daily.md` gerado ou atualizado e aprovado (com ou sem PR publicado), com a linha de timesheet na mensagem final
|
|
327
328
|
|
|
328
329
|
---
|
|
329
330
|
|
|
330
331
|
## Sessão e publicação
|
|
331
332
|
|
|
332
333
|
- O texto do PR fica em **`$SESSIONS_DIR/eng/{TASK_MANAGER_KEY}/pull-request.md`**, aprovado pelo usuário.
|
|
333
|
-
- **Publicar (push + MR/PR) só com o
|
|
334
|
-
- **A sessão nunca é apagada por este comando:** o `/eng.spec-consolidate`, que roda
|
|
334
|
+
- **Publicar (push + MR/PR) só com a escolha "Revisar o texto e publicar" (depois da aprovação do texto) ou "Publicar sem revisar o texto"** da Fase 0 (`eng.transitions-rules.md`). Com "Só gerar os arquivos", o arquivo fica na sessão e nada é enviado.
|
|
335
|
+
- **A sessão nunca é apagada por este comando:** o `/eng.spec-consolidate`, que roda depois do merge, precisa do `architecture.md`, do `plan.md`, do `gate-checklist.md` e do `pull-request.md`. A pessoa apaga à mão quando quiser.
|
|
335
336
|
|
|
336
337
|
## daily.md (registro do esforço)
|
|
337
338
|
|
|
338
|
-
- Gerado na Conclusão do `eng.pr`, em `$SESSIONS_DIR/eng/{TASK_MANAGER_KEY}/daily.md`, **
|
|
339
|
+
- Gerado na Conclusão do `eng.pr`, em `$SESSIONS_DIR/eng/{TASK_MANAGER_KEY}/daily.md`, **com ou sem PR publicado**, para não passar batido quando o PR é aberto à mão. Se houver PR (o criado nesta execução ou o já aberto da branch), preencher o campo `pr`; sem PR, deixá-lo vazio.
|
|
339
340
|
- **Sempre perguntar a data** (hoje ou retroativa) com `AskUserQuestion`.
|
|
340
341
|
- **Período de mais de um dia:** antes de contar os dias trabalhados, perguntar sobre sábado/domingo trabalhados, feriados e outros dias não trabalhados (o usuário informa as datas).
|
|
341
342
|
- ***Post mortem*:** automático com `tipo: bugfix|hotfix` no `context.md` ou com mais de 1 dia trabalhado; nos outros casos, perguntar Sim/Não.
|