jarvis-ai-framework 7.0.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +11 -34
  3. package/agents/engineering/data/eng.data-engineer.agent.md +3 -3
  4. package/agents/engineering/eng.agent.md +13 -11
  5. package/agents/engineering/eng.dev-code-reviewer.md +19 -4
  6. package/agents/engineering/eng.docs-consolidator.agent.md +2 -2
  7. package/agents/engineering/eng.docs-writer.md +17 -2
  8. package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +2 -2
  9. package/agents/engineering/qa/eng.qa.test-planner.md +20 -5
  10. package/agents/product/prod.pm-checker.md +16 -1
  11. package/bin/README.md +4 -4
  12. package/bin/commands/init.js +237 -29
  13. package/bin/lib/cli/program.js +7 -11
  14. package/bin/lib/core/sync-engine.js +8 -4
  15. package/bin/lib/utils/frontmatter.js +1 -1
  16. package/bin/lib/utils/paths.js +2 -1
  17. package/bin/lib/utils/version.js +65 -0
  18. package/bin/postinstall.js +7 -31
  19. package/package.json +2 -2
  20. package/rules/AGENTS.md +1 -1
  21. package/rules/engineering/data/data-rules.md +2 -2
  22. package/rules/engineering/eng-rules.md +1 -1
  23. package/rules/engineering/eng-security-rules.md +1 -1
  24. package/rules/engineering/eng.integrations-rules.md +6 -2
  25. package/rules/rtk-rules.md +1 -1
  26. package/rules-on-demand/doc-links-rules.md +2 -2
  27. package/rules-on-demand/engineering/eng.breakdown-subtasks-rules.md +1 -1
  28. package/rules-on-demand/engineering/eng.plan-rules.md +2 -0
  29. package/rules-on-demand/engineering/eng.pr-rules.md +8 -6
  30. package/rules-on-demand/engineering/eng.pre-pr-rules.md +10 -3
  31. package/rules-on-demand/engineering/eng.start-rules.md +7 -5
  32. package/rules-on-demand/engineering/eng.tech-spec-rules.md +9 -7
  33. package/rules-on-demand/engineering/eng.transitions-rules.md +7 -6
  34. package/skills/AGENTS.md +2 -2
  35. package/skills/SKILLS-ROADMAP.md +3 -3
  36. package/skills/eng-backend-arch-c4/assets/example-container.md +4 -4
  37. package/skills/eng-backend-arch-c4/assets/example-context.md +6 -6
  38. package/skills/eng-backend-microservices-trace/SKILL.md +1 -1
  39. package/skills/eng-global-investigation/SKILL.md +4 -0
  40. package/skills/eng-global-pr/SKILL.md +15 -12
  41. package/skills/eng-global-task-comment/SKILL.md +11 -3
  42. package/skills/eng-global-tech-spec/references/delivery-spec.md +28 -21
  43. package/skills/jarvis-docs-central/README.md +4 -4
  44. package/skills/jarvis-docs-central/SKILL.md +5 -3
  45. package/skills/jarvis-evolution-architect/references/exemplos.md +3 -3
  46. package/skills/jarvis-init/SKILL.md +89 -24
  47. package/skills/jarvis-init/assets/setup-guide.md +1 -1
  48. package/skills/jarvis-report-issue/SKILL.md +37 -24
  49. package/skills/product-roadmap-report/SKILL.md +2 -2
  50. package/skills/product-roadmap-report/commands/status.memory.md +1 -1
  51. package/skills/product-roadmap-report/commands/status.roadmap.preview.md +2 -2
  52. package/skills/product-specs/SKILL.md +1 -1
  53. package/skills/product-specs-update/SKILL.md +138 -121
  54. package/templates/ENV-template.md +57 -27
  55. package/templates/cdd-aplicado-a-prompts.md +1 -1
  56. package/templates/engineering/AGENTS-template.md +2 -2
  57. package/templates/engineering/CONTACTS-template.md +1 -1
  58. package/templates/engineering/advisor-template.md +2 -2
  59. package/templates/engineering/architecture-template.md +14 -1
  60. package/templates/engineering/data-contract-template.md +1 -1
  61. package/templates/engineering/plan-template.md +2 -0
  62. package/templates/engineering/tech-spec-delivery-template.md +8 -8
  63. package/templates/engineering/tech-spec-template.md +3 -3
  64. package/templates/product/prod-frd-template.md +1 -1
  65. package/templates/product/prod-prd-template.md +1 -1
  66. package/workflows/AGENTS.md +1 -1
  67. package/workflows/README.md +1 -1
  68. package/workflows/engineering/data/data.contract.md +12 -8
  69. package/workflows/engineering/eng.breakdown-subtasks.md +2 -2
  70. package/workflows/engineering/eng.build-tech-spec.md +1 -1
  71. package/workflows/engineering/eng.create-ard-from-code.md +3 -5
  72. package/workflows/engineering/eng.create-ard.md +3 -5
  73. package/workflows/engineering/eng.create-rfc.md +5 -5
  74. package/workflows/engineering/eng.light-arch.md +1 -1
  75. package/workflows/engineering/eng.plan.md +10 -7
  76. package/workflows/engineering/eng.pr.md +26 -19
  77. package/workflows/engineering/eng.pre-pr.md +126 -27
  78. package/workflows/engineering/eng.spec-consolidate.md +5 -5
  79. package/workflows/engineering/eng.start.md +23 -9
  80. package/workflows/engineering/eng.work.md +4 -4
  81. package/workflows/engineering/qa/eng.qa-release-signoff.md +1 -66
  82. package/workflows/product/prod.roadmap.preview.md +10 -14
  83. package/workflows/product/prod.spec.ard.md +2 -2
  84. package/workflows/product/prod.spec.breakdown.md +2 -2
  85. package/workflows/product/prod.spec.epic.md +4 -4
  86. package/workflows/product/prod.spec.frd.md +3 -3
  87. package/workflows/product/prod.spec.issue.md +3 -3
  88. package/workflows/product/prod.spec.prd.md +5 -5
  89. package/bin/commands/qa-signoff.js +0 -115
@@ -1,5 +1,5 @@
1
- import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
- import { join, dirname, basename } from "node:path";
1
+ import { existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
2
+ import { join, dirname, basename, resolve, sep } from "node:path";
3
3
  import { createInterface } from "node:readline";
4
4
  import { syncAssets } from "../lib/core/sync-engine.js";
5
5
  import { discoverAll } from "../lib/core/scanner.js";
@@ -16,6 +16,7 @@ import { getPackageInfo, getFrameworkRoot } from "../lib/utils/paths.js";
16
16
  import { logger, configureFromFlags } from "../lib/utils/logger.js";
17
17
  import { resolveEnvPath } from "../lib/env-loader.js";
18
18
  import { LOCK_FILE } from "../lib/config/constants.js";
19
+ import { compareVersions, localInstall } from "../lib/utils/version.js";
19
20
  import {
20
21
  findStaleSkills,
21
22
  registeredSpecializations,
@@ -38,6 +39,115 @@ function ask(rl, question) {
38
39
  return new Promise((resolve) => rl.question(question, resolve));
39
40
  }
40
41
 
42
+ /**
43
+ * Escolha do menu de pasta já instalada (#189). Função pura, para testar sem subprocesso.
44
+ * Com ENV.md: Enter ou 1 = atualizar (a opção recomendada), 2 = reinstalar, 3 = cancelar.
45
+ * Sem ENV.md não há o que atualizar (o upgrade precisa dele): o menu só tem 2 e 3, e Enter
46
+ * cancela, para o padrão nunca ser destrutivo (Q3). Qualquer outra resposta é inválida (Q9).
47
+ * @param {string} answer - linha digitada
48
+ * @param {{ hasEnv: boolean }} opts
49
+ * @returns {"upgrade" | "reinstall" | "cancel" | null} `null` = resposta inválida
50
+ */
51
+ export function parseMenuChoice(answer, { hasEnv }) {
52
+ const choice = String(answer).trim();
53
+ if (choice === "2") return "reinstall";
54
+ if (choice === "3") return "cancel";
55
+ if (hasEnv && (choice === "" || choice === "1")) return "upgrade";
56
+ if (!hasEnv && choice === "") return "cancel";
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * O que ainda pede o `/jarvis-init upgrade` na IDE depois do `jarvis init --upgrade` (#189, Q4).
62
+ * Só as pendências que o terminal não resolve: eixo sem forma de acesso, chave vazia, Central docs
63
+ * no formato antigo e TOKEN_TASK_MANAGER com valor. Não contam o aviso de WORKSPACE_MODE (conferir
64
+ * no ENV.md), vendor não suportado (vira freelance) nem as chaves novas acrescentadas (Q15: o
65
+ * `/jarvis-init` as põe no assunto delas na próxima vez que rodar). Nunca inclui valores do ENV.
66
+ * @param {{ pendingAxes: string[], envPlan: { pending?: string[], warnings?: string[] } }} input
67
+ * @returns {string[]} um motivo por pendência; vazio = nada pendente
68
+ */
69
+ export function upgradePendencies({ pendingAxes, envPlan }) {
70
+ const reasons = [];
71
+ if (pendingAxes.length) reasons.push(`confirmar o acesso de ${pendingAxes.join(" e ")}`);
72
+ if (envPlan.pending?.length) reasons.push(`preencher ${envPlan.pending.join(", ")}`);
73
+ const warnings = envPlan.warnings || [];
74
+ if (warnings.includes("CENTRAL_DOCS_REPO")) reasons.push("migrar o Central docs");
75
+ if (warnings.includes("TOKEN_TASK_MANAGER")) reasons.push("remover o TOKEN_TASK_MANAGER");
76
+ return reasons;
77
+ }
78
+
79
+ /**
80
+ * Leitor de linhas que não perde resposta: guarda as linhas que chegam antes da pergunta
81
+ * (pipe com várias linhas, como `printf 's\n3\n'`) e devolve `null` quando a entrada fecha
82
+ * sem linha (CI com `</dev/null`), em vez de esperar para sempre como o `rl.question` (Q2).
83
+ * @param {import("node:readline").Interface} rl
84
+ * @returns {(question: string) => Promise<string | null>}
85
+ */
86
+ function lineReader(rl) {
87
+ const lines = [];
88
+ const waiting = [];
89
+ let closed = false;
90
+ rl.on("line", (line) => (waiting.length ? waiting.shift()(line) : lines.push(line)));
91
+ rl.on("close", () => {
92
+ closed = true;
93
+ while (waiting.length) waiting.shift()(null);
94
+ });
95
+ // Sem terminal, a resposta não aparece na tela: a quebra de linha separa a pergunta do que vem depois.
96
+ const echo = (line) => {
97
+ if (line !== null && !process.stdin.isTTY) process.stdout.write("\n");
98
+ return line;
99
+ };
100
+ return (question) => {
101
+ process.stdout.write(question);
102
+ if (lines.length) return Promise.resolve(echo(lines.shift()));
103
+ if (closed) return Promise.resolve(null);
104
+ return new Promise((resolve) => waiting.push(resolve)).then(echo);
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Menu de `jarvis init` numa pasta que já tem a pasta da IDE (#189).
110
+ * Decide pela resposta lida, não por `process.stdin.isTTY`: `printf '\n' |` também atualiza.
111
+ * `askLine` é o leitor compartilhado com o menu de IDE: um leitor só para a entrada inteira,
112
+ * para a resposta de um menu não consumir a do outro (`printf '1\n3\n' | jarvis init`).
113
+ * @returns {Promise<"upgrade" | "reinstall" | "cancel" | "no-answer">} `no-answer` = entrada fechada (a mensagem já saiu)
114
+ */
115
+ async function askInstalledMenu({ folder, lockVersion, packageVersion, hasEnv, askLine }) {
116
+ const installed = lockVersion === "desconhecida" ? "versão desconhecida" : `v${lockVersion}`;
117
+ logger.info(
118
+ `${YELLOW}⚠️ Jarvis já instalado em .${folder} (${installed} · pacote v${packageVersion})${NC}`
119
+ );
120
+ logger.info("");
121
+ if (!hasEnv) {
122
+ logger.info(`${DIM}Atualizar precisa do ENV.md: reinstale e rode /jarvis-init na IDE.${NC}`);
123
+ logger.info("");
124
+ }
125
+ logger.info("O que deseja fazer?");
126
+ if (hasEnv) {
127
+ logger.info(
128
+ ` 1) Atualizar (recomendado) — sincroniza com a versão do pacote, preserva o ENV.md`
129
+ );
130
+ }
131
+ logger.info(` 2) Reinstalar — sobrescreve os arquivos do framework`);
132
+ logger.info(` 3) Cancelar`);
133
+ logger.info("");
134
+
135
+ const valid = hasEnv ? "1, 2 ou 3" : "2 ou 3";
136
+ for (;;) {
137
+ const answer = await askLine(`Escolha [${hasEnv ? "1" : "3"}]: `);
138
+ if (answer === null) {
139
+ logger.info("");
140
+ logger.info(
141
+ `${BLUE}ℹ️ Sem resposta: nada alterado. Use --upgrade (atualizar) ou --force (reinstalar).${NC}\n`
142
+ );
143
+ return "no-answer";
144
+ }
145
+ const choice = parseMenuChoice(answer, { hasEnv });
146
+ if (choice) return choice;
147
+ logger.info(`${RED}Opção inválida. Escolha ${valid}.${NC}`);
148
+ }
149
+ }
150
+
41
151
  /**
42
152
  * Grava o Context7 (MCP obrigatório) no arquivo de MCP da IDE e mostra o que falta.
43
153
  * Só avisa: problema no arquivo de MCP não muda o código de saída (instalação e --upgrade).
@@ -72,6 +182,21 @@ export async function init(flags) {
72
182
  return;
73
183
  }
74
184
 
185
+ // Um leitor de linhas só para os dois menus (IDE e pasta já instalada), criado na primeira
186
+ // pergunta e fechado antes do sync ou do upgrade (que abre o seu, só com terminal).
187
+ let reader = null;
188
+ const askLine = (question) => {
189
+ if (!reader) {
190
+ const rl = createInterface({ input: process.stdin, terminal: false });
191
+ reader = { rl, next: lineReader(rl) };
192
+ }
193
+ return reader.next(question);
194
+ };
195
+ const closeReader = () => {
196
+ reader?.rl.close();
197
+ reader = null;
198
+ };
199
+
75
200
  let ide;
76
201
  if (flags.ide) {
77
202
  if (!isIDESupported(flags.ide)) {
@@ -99,14 +224,15 @@ export async function init(flags) {
99
224
  });
100
225
  logger.info("");
101
226
 
102
- const rl = createInterface({
103
- input: process.stdin,
104
- output: process.stdout,
105
- });
106
-
107
227
  let choice;
108
228
  while (true) {
109
- choice = await ask(rl, `${BLUE}Digite o número da sua escolha (1-${IDES.length}): ${NC}`);
229
+ choice = await askLine(`${BLUE}Digite o número da sua escolha (1-${IDES.length}): ${NC}`);
230
+ if (choice === null) {
231
+ closeReader();
232
+ logger.info("");
233
+ logger.info(`${BLUE}ℹ️ Sem resposta: nada alterado. Use --ide {ide}.${NC}\n`);
234
+ return;
235
+ }
110
236
  const choiceNum = parseInt(choice, 10);
111
237
 
112
238
  if (choiceNum >= 1 && choiceNum <= IDES.length) {
@@ -127,36 +253,41 @@ export async function init(flags) {
127
253
  );
128
254
  }
129
255
  }
130
-
131
- rl.close();
132
256
  }
133
257
 
134
258
  const ideDir = join(cwd, `.${getIDEFolder(ide)}`);
135
259
 
136
- // Reinstall: --force ou confirmação "s" devem sobrescrever assets (cpSync force:true).
260
+ // Avisos de versão antes do menu: quem escolhe reinstalar (ou usa --force) já sabe se vai
261
+ // rebaixar os assets. O resultado vai para o runUpgrade, que não repete o aviso (Q8).
262
+ const lockVersion = readLockVersion(cwd);
263
+ const versionCheck = warnVersionMismatch(cwd, lockVersion);
264
+
265
+ // Reinstalação: --force ou a opção 2 do menu sobrescrevem os assets (cpSync force:true).
137
266
  let forceSync = !!flags.force;
138
267
 
139
268
  if (existsSync(ideDir) && !forceSync) {
140
- logger.info(`${YELLOW}⚠️ Framework já instalado em .${getIDEFolder(ide)}${NC}`);
141
- logger.info("");
142
-
143
- const rl = createInterface({
144
- input: process.stdin,
145
- output: process.stdout,
269
+ const choice = await askInstalledMenu({
270
+ folder: getIDEFolder(ide),
271
+ lockVersion,
272
+ packageVersion: versionCheck.running,
273
+ hasEnv: existsSync(join(ideDir, "ENV.md")),
274
+ askLine,
146
275
  });
147
- const confirm = await ask(
148
- rl,
149
- `Deseja reinstalar? Isso irá sobrescrever a instalação existente. (s/N): `
150
- );
151
- rl.close();
152
-
153
- if (confirm.toLowerCase() !== "s") {
276
+ closeReader();
277
+ if (choice === "upgrade") {
278
+ logger.info("");
279
+ process.exitCode = await runUpgrade(cwd, { ...flags, ide, versionCheck });
280
+ return;
281
+ }
282
+ if (choice === "no-answer") return;
283
+ if (choice === "cancel") {
154
284
  logger.info(`${BLUE}ℹ️ Instalação cancelada.${NC}\n`);
155
285
  return;
156
286
  }
157
287
  forceSync = true;
158
288
  logger.info("");
159
289
  }
290
+ closeReader();
160
291
 
161
292
  const ideConfig = getIDEConfig(ide);
162
293
  logger.info(`${BLUE}Configurando ambiente para ${ideConfig?.name}...${NC}`);
@@ -164,13 +295,15 @@ export async function init(flags) {
164
295
  const result = syncAssets(cwd, ide, {
165
296
  force: forceSync,
166
297
  workflowsFolder: getWorkflowsFolder(ide),
298
+ // Reinstalação não troca valor do ENV.md (RTK_ENABLED=false fica), como o upgrade (Q1).
299
+ keepEnvValues: forceSync,
167
300
  });
168
301
 
169
302
  for (const item of result.copied) {
170
303
  logger.info(`${BLUE} ↳ ${item}${NC}`);
171
304
  }
172
305
  for (const item of result.skipped) {
173
- logger.info(`${DIM} ↳ ${item} (já existe)${NC}`);
306
+ logger.info(`${DIM} ↳ ${item}${NC}`); // o motivo já vem no item (sync-engine)
174
307
  }
175
308
  for (const item of result.errors) {
176
309
  logger.error(`${RED} ✗ ${item}${NC}`);
@@ -257,6 +390,53 @@ function ideFromEnvPath(envPath) {
257
390
  return getIDEValues().find((v) => `.${getIDEFolder(v)}` === folder);
258
391
  }
259
392
 
393
+ /**
394
+ * Avisos de versão antes do sync. O Jarvis é instalado só global:
395
+ * - cópia local no projeto (`node_modules` ou dependência no `package.json`) é ignorada;
396
+ * se é ela que está rodando (`npx jarvis`, `./node_modules/.bin/jarvis`), o aviso diz isso;
397
+ * - lock do projeto mais novo que o Jarvis em uso: o sync rebaixaria os assets.
398
+ * Recebe o lock lido antes do `syncAssets`, que o reescreve.
399
+ * @param {string} workspaceDir
400
+ * @param {string} [lock] - versão do `jarvis-lock.json` do projeto
401
+ * @returns {{ downgrade: boolean, running: string }} `downgrade` = lock mais novo que o Jarvis em uso
402
+ */
403
+ export function warnVersionMismatch(workspaceDir, lock = readLockVersion(workspaceDir)) {
404
+ const running = getPackageInfo().version;
405
+ // Caminhos reais dos dois lados: o Node já resolve o link do script (`import.meta.url`),
406
+ // então uma cópia por `npm link` em node_modules só bate com o realpath.
407
+ const real = (p) => {
408
+ try {
409
+ return realpathSync(p);
410
+ } catch {
411
+ return resolve(p);
412
+ }
413
+ };
414
+ const root = real(getFrameworkRoot());
415
+ const localDir = join(workspaceDir, "node_modules", "jarvis-ai-framework");
416
+ const runningLocal =
417
+ (root + sep).startsWith(real(join(workspaceDir, "node_modules")) + sep) ||
418
+ (existsSync(localDir) && root === real(localDir));
419
+ const local = localInstall(workspaceDir);
420
+ if (runningLocal) {
421
+ logger.info(
422
+ `${BLUE}ℹ️ O Jarvis em uso (${running}) é o mesmo de node_modules (cópia local do projeto). O Jarvis é instalado global: npm install -g jarvis-ai-framework, rode \`jarvis\` (sem npx) e remova a cópia local.${NC}`
423
+ );
424
+ } else if (local) {
425
+ logger.info(
426
+ `${BLUE}ℹ️ O Jarvis é instalado global (npm install -g jarvis-ai-framework). A cópia local deste projeto (${local.version} em ${local.source}) é ignorada: pode removê-la.${NC}`
427
+ );
428
+ }
429
+ const downgrade = compareVersions(lock, running) === 1;
430
+ if (downgrade) {
431
+ const who = runningLocal ? "o Jarvis em uso (cópia local)" : "o seu jarvis global";
432
+ logger.info(
433
+ `${YELLOW}⚠️ Os assets deste projeto são da ${lock}, mas ${who} é a ${running}. Atualize o global: npm install -g jarvis-ai-framework${NC}`
434
+ );
435
+ }
436
+ if (runningLocal || local || downgrade) logger.info("");
437
+ return { downgrade, running };
438
+ }
439
+
260
440
  function readLockVersion(workspaceDir) {
261
441
  try {
262
442
  return (
@@ -319,6 +499,29 @@ async function runUpgrade(cwd, flags) {
319
499
  const workspaceDir = resolved.root;
320
500
  const envPath = resolved.path;
321
501
  const previousVersion = readLockVersion(workspaceDir);
502
+ // Vindo do menu do `init`, o aviso já saiu antes da escolha (Q8): só reaproveita o resultado.
503
+ const { downgrade, running } =
504
+ flags.versionCheck ?? warnVersionMismatch(workspaceDir, previousVersion);
505
+ if (downgrade && !flags.yes) {
506
+ let go = false;
507
+ if (process.stdin.isTTY) {
508
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
509
+ const answer = await ask(
510
+ rl,
511
+ `Seguir e rebaixar os assets de ${previousVersion} para ${running}? (s/N): `
512
+ );
513
+ rl.close();
514
+ go = answer.trim().toLowerCase() === "s";
515
+ } else {
516
+ logger.info(`${DIM} Sem terminal interativo e sem --yes: nada será rebaixado.${NC}`);
517
+ }
518
+ if (!go) {
519
+ logger.info(
520
+ `${YELLOW}Upgrade interrompido: atualize o global (npm install -g jarvis-ai-framework) e rode de novo.${NC}\n`
521
+ );
522
+ return 1;
523
+ }
524
+ }
322
525
 
323
526
  // 1. Arquivos do framework (o ENV.md não está entre os sincronizados)
324
527
  logger.info(`${BLUE}⬆️ Upgrade do Jarvis em .${getIDEFolder(ide)}/${NC}\n`);
@@ -407,7 +610,11 @@ async function runUpgrade(cwd, flags) {
407
610
  // 4. Resumo (só nomes de chave e caminhos)
408
611
  const pkg = getPackageInfo();
409
612
  logger.info("");
410
- logger.info(`${GREEN}✅ Upgrade concluído: ${previousVersion} → ${pkg.version}${NC}`);
613
+ logger.info(
614
+ previousVersion === pkg.version
615
+ ? `${GREEN}✅ Upgrade concluído: já na v${pkg.version} (arquivos ressincronizados)${NC}`
616
+ : `${GREEN}✅ Upgrade concluído: ${previousVersion} → ${pkg.version}${NC}`
617
+ );
411
618
  logger.info(` • Arquivos do framework: ${sync.copied.length} item(ns) sincronizado(s)`);
412
619
  logger.info(
413
620
  ` • ENV.md: ${addedKeys.length ? `+ ${addedKeys.join(", ")}` : "nenhuma variável nova"}`
@@ -459,11 +666,12 @@ async function runUpgrade(cwd, flags) {
459
666
  const access = (futureEntries.get(`${key}_ACCESS`) || "").trim();
460
667
  return getVendor(futureEntries.get(key)) && (!access || /^\[.*\]$/.test(access));
461
668
  });
669
+ const pending = upgradePendencies({ pendingAxes, envPlan });
462
670
  logger.info("");
463
671
  logger.info(
464
- pendingAxes.length
465
- ? `${CYAN}➡️ Agora rode ${BOLD}/jarvis-init upgrade${NC}${CYAN} na IDE para confirmar o acesso de ${pendingAxes.join(" e ")} (e as variáveis).${NC}\n`
466
- : `${CYAN}➡️ Agora rode ${BOLD}/jarvis-init upgrade${NC}${CYAN} na IDE.${NC}\n`
672
+ pending.length
673
+ ? `${CYAN}➡️ Agora rode ${BOLD}/jarvis-init upgrade${NC}${CYAN} na IDE: ${pending.join("; ")}.${NC}\n`
674
+ : `${GREEN}✓ Nada pendente na IDE.${NC}\n`
467
675
  );
468
676
  return sync.errors.length || moveErrors.length || staleErrors.length ? 1 : 0;
469
677
  }
@@ -22,7 +22,6 @@ import { info } from "../../commands/info.js";
22
22
  import { whoami } from "../../commands/whoami.js";
23
23
  import { docsSync } from "../../commands/docs-sync.js";
24
24
  import { docsPublish } from "../../commands/docs-publish.js";
25
- import { qaSignoff } from "../../commands/qa-signoff.js";
26
25
  import { installRtk } from "../../commands/install-rtk.js";
27
26
  import { tokens } from "../../commands/tokens.js";
28
27
  import { map } from "../../commands/map.js";
@@ -88,15 +87,18 @@ export function buildProgram() {
88
87
  const initCmd = cmd(program, "init")
89
88
  .description("Bootstrap do framework no workspace")
90
89
  .option("--ide <ide>", "IDE a configurar (pula o menu interativo)")
91
- .option("--force", "Sobrescreve a instalação existente sem perguntar")
90
+ .option("--force", "Reinstala sem mostrar o menu: sobrescreve os arquivos do framework")
92
91
  .option(
93
92
  "--upgrade",
94
- "Atualiza um projeto já configurado: arquivos do framework, variáveis novas do ENV e pastas de documentos"
93
+ "Atualiza sem mostrar o menu: arquivos do framework, variáveis novas do ENV e pastas de documentos"
94
+ )
95
+ .option(
96
+ "--yes",
97
+ "Com --upgrade (ou a opção Atualizar do menu), move os documentos para as pastas novas e aceita rebaixar os assets sem perguntar"
95
98
  )
96
- .option("--yes", "Com --upgrade, move os documentos para as pastas novas sem perguntar")
97
99
  .option(
98
100
  "--remove-stale",
99
- "Com --upgrade, remove sem perguntar os skills instalados que saíram do framework (só os listados)"
101
+ "Com --upgrade (ou a opção Atualizar do menu), remove sem perguntar os skills instalados que saíram do framework (só os listados)"
100
102
  );
101
103
  withLogOptions(initCmd).action((options) => init(toLegacyFlags(options)));
102
104
 
@@ -162,12 +164,6 @@ export function buildProgram() {
162
164
  );
163
165
  withLogOptions(installRtkCmd).action((options) => installRtk(toLegacyFlags(options)));
164
166
 
165
- const qaSignoffCmd = cmd(program, "qa-signoff")
166
- .description("Verificar sign-off QA válido para a branch atual (usado pelo CI)")
167
- .option("--branch <branch>", "Branch a verificar (padrão: lido do git)")
168
- .option("--max-age <horas>", "Validade máxima em horas (padrão: 24)");
169
- withEnvOptions(qaSignoffCmd).action((options) => qaSignoff(toLegacyFlags(options)));
170
-
171
167
  cmd(program, "tokens")
172
168
  .description("Estimar o que carrega no início da sessão")
173
169
  .option("--hub <hub>")
@@ -80,7 +80,7 @@ export function syncAssets(targetDir, ide, opts = {}) {
80
80
  const dest = join(ideDir, destDirName);
81
81
 
82
82
  if (!existsSync(src)) {
83
- result.skipped.push(`${dir}/ (not found in framework)`);
83
+ result.skipped.push(`${dir}/ (não existe no framework)`);
84
84
  continue;
85
85
  }
86
86
 
@@ -102,7 +102,7 @@ export function syncAssets(targetDir, ide, opts = {}) {
102
102
  if (!existsSync(src)) continue;
103
103
 
104
104
  if (existsSync(dest) && !opts.force) {
105
- result.skipped.push(`${file} (already exists)`);
105
+ result.skipped.push(`${file} (já existe)`);
106
106
  continue;
107
107
  }
108
108
 
@@ -228,7 +228,11 @@ function reconcileRtk(ideDir, targetDir, ide, { keepEnvValues = false } = {}) {
228
228
  return "(rtk) detectado — RTK_ENABLED=true no ENV.md";
229
229
  }
230
230
  if (!env.includes("RTK_ENABLED")) {
231
- env = env.trimEnd() + "\n\n# --- RTK — Token Killer ---\nRTK_ENABLED=true\n";
231
+ // Moldura do ENV-template (#189): o /jarvis-init não aponta o bloco como separador antigo.
232
+ const bar =
233
+ "# =============================================================================";
234
+ env =
235
+ env.trimEnd() + `\n\n${bar}\n# RTK — Token Killer (opcional)\n${bar}\nRTK_ENABLED=true\n`;
232
236
  writeFileSync(envPath, env, "utf-8");
233
237
  return "(rtk) detectado — RTK_ENABLED=true adicionado ao ENV.md";
234
238
  }
@@ -575,7 +579,7 @@ function writeLockFile(targetDir, ideDir, ide, frameworkRoot) {
575
579
 
576
580
  const lockContent = JSON.stringify(lock, null, 2) + "\n";
577
581
 
578
- // Write lock file to project root (for postinstall.js)
582
+ // Write lock file to project root (lido pelo jarvis init --upgrade e pelo Passo 0 do /jarvis-init)
579
583
  writeFileSync(join(targetDir, LOCK_FILE), lockContent);
580
584
 
581
585
  // Also write lock file to IDE folder
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Leitura de frontmatter YAML (`---\n...\n---`) compartilhada
3
- * entre scanner.js, validate-frontmatter.js, qa-signoff.js e sync-engine.js —
3
+ * entre scanner.js, validate-frontmatter.js e sync-engine.js —
4
4
  * cada um tinha sua própria cópia da mesma regex de bloco.
5
5
  * @module utils/frontmatter
6
6
  */
@@ -18,7 +18,8 @@ export function getFrameworkRoot() {
18
18
  }
19
19
 
20
20
  throw new Error(
21
- "Could not locate framework assets.\n" + "Run: npm install jarvis-framework --save-dev"
21
+ "Não foi possível localizar os assets do framework.\n" +
22
+ "Instale o Jarvis global: npm install -g jarvis-ai-framework"
22
23
  );
23
24
  }
24
25
 
@@ -0,0 +1,65 @@
1
+ import { lstatSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const PACKAGE = "jarvis-ai-framework";
5
+
6
+ /**
7
+ * Compara duas versões `x.y.z` (aceita `v` na frente e ignora o sufixo, como `-beta.1`).
8
+ * @param {string} a
9
+ * @param {string} b
10
+ * @returns {-1 | 0 | 1 | null} `null` se uma das duas não for uma versão legível
11
+ */
12
+ export function compareVersions(a, b) {
13
+ const parse = (v) => {
14
+ const m = String(v ?? "")
15
+ .trim()
16
+ .match(/^v?(\d+)\.(\d+)\.(\d+)/);
17
+ return m ? m.slice(1).map(Number) : null;
18
+ };
19
+ const pa = parse(a);
20
+ const pb = parse(b);
21
+ if (!pa || !pb) return null;
22
+ for (let i = 0; i < 3; i++) {
23
+ if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
24
+ }
25
+ return 0;
26
+ }
27
+
28
+ // lstat não segue o link: um link simbólico quebrado também conta como presente.
29
+ function exists(path) {
30
+ try {
31
+ lstatSync(path);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ function readJson(path) {
39
+ try {
40
+ return JSON.parse(readFileSync(path, "utf-8"));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Cópia local do Jarvis no projeto, que é ignorada (o Jarvis é instalado só global).
48
+ * @param {string} workspaceDir
49
+ * @returns {{ version: string, source: string } | null} a versão instalada em `node_modules`
50
+ * ("desconhecida" se ilegível), ou a faixa declarada no `package.json`; `null` sem nenhuma das duas
51
+ */
52
+ export function localInstall(workspaceDir) {
53
+ const installed = join(workspaceDir, "node_modules", PACKAGE, "package.json");
54
+ if (exists(join(workspaceDir, "node_modules", PACKAGE))) {
55
+ // Pasta (ou link, mesmo quebrado) presente, sem package.json legível: ainda é uma cópia local.
56
+ const version = readJson(installed)?.version || "desconhecida";
57
+ return { version, source: `node_modules/${PACKAGE}` };
58
+ }
59
+ const pkg = readJson(join(workspaceDir, "package.json"));
60
+ for (const field of ["dependencies", "devDependencies"]) {
61
+ const range = pkg?.[field]?.[PACKAGE];
62
+ if (range) return { version: range, source: `package.json (${field})` };
63
+ }
64
+ return null;
65
+ }
@@ -1,41 +1,17 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
1
  import { execSync } from "node:child_process";
3
- import { join, dirname } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
2
 
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
-
9
- const cwd = process.env.INIT_CWD || process.cwd();
10
- const lockPath = join(cwd, "jarvis-lock.json");
11
-
12
- // ── Sincronizar assets para projetos com jarvis-lock.json ───────────────────
13
- if (existsSync(lockPath)) {
14
- try {
15
- const lock = JSON.parse(readFileSync(lockPath, "utf-8"));
16
- const { syncAssets } = await import("./lib/core/sync-engine.js");
17
- const { getWorkflowsFolder } = await import("./lib/config/ide-config.js");
18
- const { logger } = await import("./lib/utils/logger.js");
19
- logger.info(`\n jarvis-framework — syncing to .${lock.ide}/\n`);
20
- const result = syncAssets(cwd, lock.ide, {
21
- force: true,
22
- workflowsFolder: getWorkflowsFolder(lock.ide),
23
- });
24
- logger.info(` ${result.copied.length} items synced.\n`);
25
- } catch {}
26
- } else {
27
- const { logger } = await import("./lib/utils/logger.js");
28
- logger.info(`
29
- jarvis-framework installed.
30
- Run: npx jarvis init --ide windsurf
3
+ // O Jarvis é instalado só global (npm install -g); o postinstall só dá as dicas.
4
+ // A atualização dos assets de um projeto tem um caminho só: jarvis init --upgrade, dentro dele.
5
+ const { logger } = await import("./lib/utils/logger.js");
6
+ logger.info(`
7
+ jarvis-ai-framework instalado.
8
+ No projeto: jarvis init --ide {ide} (ou jarvis init --upgrade para atualizar os assets)
31
9
  `);
32
- }
33
10
 
34
11
  // ── Sugestão RTK (sempre, se rtk não estiver instalado) ─────────────────────
35
12
  try {
36
13
  execSync("rtk gain --help 2>/dev/null", { stdio: "pipe" });
37
14
  } catch {
38
- const { logger } = await import("./lib/utils/logger.js");
39
15
  logger.info(` 💡 Dica: instale o RTK para economizar 60-90% dos tokens de shell.`);
40
- logger.info(` npx jarvis install-rtk\n`);
16
+ logger.info(` jarvis install-rtk\n`);
41
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jarvis-ai-framework",
3
- "version": "7.0.0",
3
+ "version": "8.2.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 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 test/session-scan.test.js test/framework-refs.test.js test/validate-frontmatter.test.js test/mcp-config.test.js test/integrations-catalog.test.js test/cli-runner.test.js test/tasks-comment.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 test/session-scan.test.js test/framework-refs.test.js test/validate-frontmatter.test.js test/mcp-config.test.js test/integrations-catalog.test.js test/cli-runner.test.js test/tasks-comment.test.js test/version.test.js test/gate-checklist-rodadas.test.js test/publish-comments-rules.test.js test/pre-pr-criterio.test.js test/nomes-soltos.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
@@ -169,7 +169,7 @@ Links para documentacao relacionada.
169
169
  |------|----------|-----------|
170
170
  | Planejamento | `eng.start`, `eng.plan` | Somente analise, SEM codigo |
171
171
  | Implementacao | `eng.work` | Codigo e testes, commit por fase, SEM push nem PR |
172
- | Entrega | `eng.pr` | Branch, commit e PR |
172
+ | Entrega | `eng.pr` | Push e PR, depois da pergunta (regra de ouro) |
173
173
 
174
174
  ### Seguranca
175
175
 
@@ -67,7 +67,7 @@ expect_table_row_count_to_be_between(min_value=last_run_count * 0.5)
67
67
  ### Falha de qualidade
68
68
 
69
69
  - **Bronze → Silver**: falha bloqueia a promoção. Dados permanecem em bronze.
70
- - **Silver → Gold**: falha bloqueia publicação. Alerta no Slack canal do time de Data.
70
+ - **Silver → Gold**: falha bloqueia publicação. Alerta automático do pipeline no Slack canal do time de Data (runtime; um aviso enviado pelo Jarvis só sai depois da pergunta, regra de ouro).
71
71
  - Nunca silenciar falhas — registrar no log com contexto da execução.
72
72
 
73
73
  ---
@@ -116,7 +116,7 @@ logger.info({
116
116
 
117
117
  - **AWS Glue**: logs automáticos no CloudWatch (`/aws-glue/jobs/output`)
118
118
  - **Airflow**: logs nas tasks do DAG + CloudWatch quando em produção
119
- - **Erros críticos**: alertar no canal Slack do time de Data (`#data-alerts` ou equivalente)
119
+ - **Erros críticos**: alerta automático do pipeline no canal Slack do time de Data (`#data-alerts` ou equivalente; runtime. Um aviso enviado pelo Jarvis só sai depois da pergunta, regra de ouro)
120
120
 
121
121
  ### Retenção
122
122
 
@@ -65,7 +65,7 @@ env_file: "@/ENV.md"
65
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
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
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, SPEC, discoveries, Central docs), fora do versionamento. Leia-a sob demanda, como documento de trabalho, e não como código.
68
+ - **`docs/jarvis/` não é ignorada**: guarda os documentos de trabalho gerados (PRD, FRD, ARD, RFC, SPEC, discoveries) e, com storage `filesystem`, a cópia local do Central docs (`central-docs/`, no local padrão; o registro do implementado), fora do versionamento. Leia-a sob demanda, como documento de trabalho ou registro, e não como código.
69
69
  - **Pastas de outras IDEs** nunca são lidas (vale o "Isolamento de IDE" acima).
70
70
  - Com `WORKSPACE_MODE=project`, o git já delimita o código e esta regra não age.
71
71
 
@@ -105,7 +105,7 @@ O agente DEVE incluir no checklist de pre-PR as seguintes verificacoes de segura
105
105
  - [ ] **Outputs**: erros nao vazam detalhes internos
106
106
 
107
107
  Se o PR toca em areas sensiveis (auth, sessions, RBAC, CORS, CSP, permissoes), o agente DEVE:
108
- 1. Adicionar label `security` ao PR
108
+ 1. Registrar no PR Brief do `gate-checklist.md` que o PR leva o label `security` e orientar a pessoa a adiciona-lo ao PR (o Jarvis nao altera o PR no vendor sem perguntar, regra de ouro; o `jarvis vcs create-merge` nao poe label)
109
109
  2. Seguir o gate de seguranca inline do `eng.pre-pr` (sanitizacao, auth guards, headers, secrets scan, `npm audit`) antes do merge
110
110
 
111
111
  ---