jarvis-ai-framework 2.1.1 → 3.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 (266) hide show
  1. package/AGENTS.md +14 -115
  2. package/agents/AGENTS.md +11 -23
  3. package/agents/README.md +27 -129
  4. package/agents/engineering/data/eng.data-engineer.agent.md +11 -34
  5. package/agents/engineering/eng.advisor.agent.md +56 -0
  6. package/agents/engineering/eng.agent.md +14 -24
  7. package/agents/engineering/eng.bug-hunter.md +7 -15
  8. package/agents/engineering/eng.dev-code-reviewer.md +1 -1
  9. package/agents/engineering/eng.frontend.agent.md +3 -7
  10. package/agents/engineering/eng.rpa.agent.md +2 -8
  11. package/agents/engineering/qa/eng.qa.quality-strategist.md +6 -11
  12. package/agents/engineering/qa/eng.qa.test-architect.md +8 -18
  13. package/agents/engineering/qa/eng.qa.test-planner.md +10 -14
  14. package/agents/engineering/qa/eng.qa.testing-engineer.md +91 -24
  15. package/agents/product/prod.discovery-interviewer.md +3 -3
  16. package/bin/README.md +142 -0
  17. package/bin/commands/docs-publish.js +105 -141
  18. package/bin/commands/docs-sync.js +66 -104
  19. package/bin/commands/info.js +7 -27
  20. package/bin/commands/init.js +23 -51
  21. package/bin/commands/install-rtk.js +9 -3
  22. package/bin/commands/list.js +5 -18
  23. package/bin/commands/map.js +20 -5
  24. package/bin/commands/qa-signoff.js +58 -55
  25. package/bin/commands/tokens.js +14 -17
  26. package/bin/commands/whoami.js +30 -26
  27. package/bin/jarvis.js +9 -163
  28. package/bin/lib/auth/session.js +20 -20
  29. package/bin/lib/cli/program.js +219 -0
  30. package/bin/lib/cli/shared-options.js +29 -0
  31. package/bin/lib/config/constants.js +59 -2
  32. package/bin/lib/config/ide-config.js +4 -8
  33. package/bin/lib/core/profile-filter.js +4 -1
  34. package/bin/lib/core/scanner.js +3 -29
  35. package/bin/lib/core/sync-engine.js +21 -14
  36. package/bin/lib/core/token-report.js +12 -2
  37. package/bin/lib/docs/fetch-file.js +39 -0
  38. package/bin/lib/docs/publish-file.js +194 -0
  39. package/bin/lib/docs/redis-cache.js +49 -0
  40. package/bin/lib/docs/validate-frontmatter.js +9 -39
  41. package/bin/lib/env-loader.js +11 -34
  42. package/bin/lib/flow-map/parse.js +11 -5
  43. package/bin/lib/flow-map/render.js +5 -2
  44. package/bin/lib/flow-map/tree.js +59 -10
  45. package/bin/lib/tasks/comment.js +40 -35
  46. package/bin/lib/utils/frontmatter.js +57 -0
  47. package/bin/lib/utils/git-parser.js +4 -6
  48. package/bin/lib/utils/npmrc-parser.js +6 -13
  49. package/bin/lib/utils/paths.js +2 -4
  50. package/bin/lib/utils/ui.js +3 -9
  51. package/bin/lib/vcs/api.js +25 -41
  52. package/bin/lib/vcs/create-issue.js +49 -33
  53. package/bin/lib/vcs/create-merge.js +47 -31
  54. package/bin/lib/vcs/fetch-raw.js +35 -23
  55. package/package.json +14 -5
  56. package/rules/engineering/eng-rules.md +1 -1
  57. package/rules/engineering/eng-security-rules.md +18 -16
  58. package/rules/engineering/eng.bump-rules.md +3 -2
  59. package/rules/engineering/eng.docs-scraping-rules.md +4 -4
  60. package/rules/engineering/eng.integrations-rules.md +6 -4
  61. package/rules/engineering/eng.skills-rules.md +1 -1
  62. package/rules/engineering/eng.specializations-rules.md +18 -4
  63. package/rules/engineering/frontend/eng.frontend-rules.md +1 -4
  64. package/rules/engineering/qa/eng.qa.cypress-standards-rules.md +1 -1
  65. package/rules/engineering/rpa/eng.rpa-rules.md +4 -9
  66. package/rules-on-demand/engineering/eng.pre-pr-rules.md +1 -1
  67. package/rules-on-demand/engineering/eng.start-rules.md +2 -0
  68. package/skills/AGENTS.md +52 -87
  69. package/skills/SKILLS-ROADMAP.md +47 -128
  70. package/skills/eng-ai/SKILL.md +270 -0
  71. package/skills/eng-ai/references/1-fundamentos-ml.md +24 -0
  72. package/skills/eng-ai/references/10-arquitetura-solucoes-ia.md +24 -0
  73. package/skills/eng-ai/references/11-governanca-ia.md +24 -0
  74. package/skills/eng-ai/references/12-seguranca-ia-responsavel.md +25 -0
  75. package/skills/eng-ai/references/2-matematica-estatistica.md +24 -0
  76. package/skills/eng-ai/references/3-dados-para-ia.md +24 -0
  77. package/skills/eng-ai/references/4-deep-learning-fundacionais.md +24 -0
  78. package/skills/eng-ai/references/5-llms-chatbots-agentes-rag.md +25 -0
  79. package/skills/eng-ai/references/6-avaliacao-experimentacao.md +24 -0
  80. package/skills/eng-ai/references/7-engenharia-sistemas-ia.md +24 -0
  81. package/skills/eng-ai/references/8-mlops-plataforma.md +24 -0
  82. package/skills/eng-ai/references/9-produto-ia.md +24 -0
  83. package/skills/eng-ai/references/especializacoes.md +17 -0
  84. package/skills/eng-automation/SKILL.md +290 -0
  85. package/skills/eng-automation/references/1-fundamentos-automacao.md +25 -0
  86. package/skills/eng-automation/references/10-ingestao-dados.md +25 -0
  87. package/skills/eng-automation/references/11-captcha-antibot.md +25 -0
  88. package/skills/eng-automation/references/12-limites-taxa-sessoes.md +25 -0
  89. package/skills/eng-automation/references/13-agendamento-orquestracao.md +24 -0
  90. package/skills/eng-automation/references/14-retentativa-erros.md +25 -0
  91. package/skills/eng-automation/references/15-autenticacao-credenciais.md +24 -0
  92. package/skills/eng-automation/references/16-observabilidade-confiabilidade.md +24 -0
  93. package/skills/eng-automation/references/2-aquisicao-dados.md +25 -0
  94. package/skills/eng-automation/references/3-automacao-navegador.md +24 -0
  95. package/skills/eng-automation/references/4-scraping-crawling.md +25 -0
  96. package/skills/eng-automation/references/5-apis-rest-graphql-soap.md +26 -0
  97. package/skills/eng-automation/references/6-webhooks-integracao.md +25 -0
  98. package/skills/eng-automation/references/7-rpa.md +25 -0
  99. package/skills/eng-automation/references/8-extracao-estruturada.md +25 -0
  100. package/skills/eng-automation/references/9-extracao-documentos.md +25 -0
  101. package/skills/eng-automation/references/especializacoes.md +18 -0
  102. package/skills/eng-backend-nestjs/SKILL.md +1 -1
  103. package/skills/eng-data/SKILL.md +271 -0
  104. package/skills/eng-data/references/1-fundamentos-dados.md +24 -0
  105. package/skills/eng-data/references/10-ciencia-dados.md +24 -0
  106. package/skills/eng-data/references/11-dataops-plataforma.md +24 -0
  107. package/skills/eng-data/references/12-seguranca-privacidade.md +24 -0
  108. package/skills/eng-data/references/2-modelagem-dados.md +24 -0
  109. package/skills/eng-data/references/3-bancos-dados.md +24 -0
  110. package/skills/eng-data/references/4-arquitetura-dados.md +24 -0
  111. package/skills/eng-data/references/5-pipelines-integracao.md +24 -0
  112. package/skills/eng-data/references/6-qualidade-dados.md +24 -0
  113. package/skills/eng-data/references/7-governanca-metadados.md +24 -0
  114. package/skills/eng-data/references/8-estatistica-analise.md +24 -0
  115. package/skills/eng-data/references/9-analytics-bi.md +24 -0
  116. package/skills/eng-data/references/especializacoes.md +20 -0
  117. package/skills/eng-frontend/SKILL.md +12 -5
  118. package/skills/eng-frontend/references/13-design-system.md +48 -0
  119. package/skills/eng-frontend/references/14-microfrontend.md +46 -0
  120. package/skills/eng-global-task-comment/SKILL.md +3 -3
  121. package/skills/eng-platform/SKILL.md +331 -0
  122. package/skills/eng-platform/references/1-fundamentos-sistemas.md +23 -0
  123. package/skills/eng-platform/references/10-resiliencia-arquitetura.md +24 -0
  124. package/skills/eng-platform/references/11-plataforma-devex.md +24 -0
  125. package/skills/eng-platform/references/12-finops-custos.md +24 -0
  126. package/skills/eng-platform/references/13-seguranca-fundamentos.md +26 -0
  127. package/skills/eng-platform/references/14-profiling-performance.md +23 -0
  128. package/skills/eng-platform/references/15-testes-carga-chaos.md +23 -0
  129. package/skills/eng-platform/references/16-cache-multicamada.md +22 -0
  130. package/skills/eng-platform/references/2-redes.md +23 -0
  131. package/skills/eng-platform/references/3-cloud.md +24 -0
  132. package/skills/eng-platform/references/4-iac-automacao.md +24 -0
  133. package/skills/eng-platform/references/5-containers-orquestracao.md +25 -0
  134. package/skills/eng-platform/references/6-cicd-entrega.md +24 -0
  135. package/skills/eng-platform/references/7-observabilidade.md +24 -0
  136. package/skills/eng-platform/references/8-confiabilidade-sre.md +24 -0
  137. package/skills/eng-platform/references/9-seguranca-infra.md +26 -0
  138. package/skills/eng-platform/references/especializacoes.md +34 -0
  139. package/skills/eng-qa/SKILL.md +283 -0
  140. package/skills/eng-qa/references/1-fundamentos-qualidade.md +25 -0
  141. package/skills/eng-qa/references/10-confiabilidade.md +25 -0
  142. package/skills/eng-qa/references/11-seguranca.md +24 -0
  143. package/skills/eng-qa/references/12-acessibilidade.md +24 -0
  144. package/skills/eng-qa/references/13-dados-testes.md +24 -0
  145. package/skills/eng-qa/references/14-gestao-testes-defeitos.md +25 -0
  146. package/skills/eng-qa/references/15-arquitetura-qa.md +25 -0
  147. package/skills/eng-qa/references/2-estrategia-planejamento.md +25 -0
  148. package/skills/eng-qa/references/3-design-testes.md +25 -0
  149. package/skills/eng-qa/references/4-manual-exploratorio.md +24 -0
  150. package/skills/eng-qa/references/5-automacao-testes.md +25 -0
  151. package/skills/eng-qa/references/6-api-integracao.md +25 -0
  152. package/skills/eng-qa/references/7-contrato.md +24 -0
  153. package/skills/eng-qa/references/8-interface-compat-mobile.md +24 -0
  154. package/skills/eng-qa/references/9-performance.md +24 -0
  155. package/skills/eng-qa/references/especializacoes.md +29 -0
  156. package/skills/eng-qa-planner/SKILL.md +264 -0
  157. package/skills/jarvis-context-detect/SKILL.md +1 -1
  158. package/skills/jarvis-context-detect/assets/context-profile-template.md +1 -1
  159. package/skills/jarvis-create-specialization/SKILL.md +8 -0
  160. package/skills/jarvis-docs-central/README.md +31 -28
  161. package/skills/jarvis-docs-central/SKILL.md +5 -5
  162. package/skills/jarvis-init/SKILL.md +9 -8
  163. package/skills/jarvis-init/assets/passo-adicionar-stacks.md +47 -18
  164. package/skills/jarvis-list-specializations/SKILL.md +30 -17
  165. package/skills/jarvis-report-issue/SKILL.md +1 -1
  166. package/taxonomy.md +4 -16
  167. package/templates/AGENTS.md +2 -0
  168. package/templates/ENV-template.md +20 -2
  169. package/templates/engineering/advisor-template.md +65 -0
  170. package/templates/engineering/architecture-template.md +52 -4
  171. package/templates/engineering/qa/qa.sprint-plan-template.md +1 -1
  172. package/workflows/AGENTS.md +3 -1
  173. package/workflows/README.md +0 -10
  174. package/workflows/engineering/data/data.new-pipeline.md +6 -5
  175. package/workflows/engineering/eng.bug-audit.md +4 -14
  176. package/workflows/engineering/eng.debug.md +4 -20
  177. package/workflows/engineering/eng.plan.md +4 -26
  178. package/workflows/engineering/eng.pr.md +13 -63
  179. package/workflows/engineering/eng.pre-pr.md +14 -51
  180. package/workflows/engineering/eng.review.md +7 -13
  181. package/workflows/engineering/eng.rpa.robot.md +3 -9
  182. package/workflows/engineering/eng.start.md +37 -32
  183. package/workflows/engineering/eng.work.md +17 -40
  184. package/workflows/engineering/qa/eng.qa-sprint-planning.md +1 -1
  185. package/workflows/product/prod.spec.discovery.md +2 -2
  186. package/workflows/warm-up.md +35 -63
  187. package/agents/engineering/eng.cybersecurity.agent.md +0 -486
  188. package/agents/engineering/qa/eng.qa.cypress-specialist.md +0 -112
  189. package/bin/lib/docs/fetch-file.sh +0 -41
  190. package/bin/lib/docs/publish-file.sh +0 -284
  191. package/rules/engineering/qa/eng.qa.exploratory-session-rules.md +0 -137
  192. package/rules/engineering/qa/eng.qa.quality-gate-scoring-rules.md +0 -181
  193. package/rules/engineering/qa/eng.qa.tech-spec-validation-criteria-rules.md +0 -120
  194. package/skills/eng-ai-engineer/SKILL.md +0 -303
  195. package/skills/eng-data-bi/SKILL.md +0 -206
  196. package/skills/eng-data-debug/SKILL.md +0 -314
  197. package/skills/eng-data-engineer/SKILL.md +0 -266
  198. package/skills/eng-data-onboard/SKILL.md +0 -317
  199. package/skills/eng-data-orchestrator/SKILL.md +0 -433
  200. package/skills/eng-devops-performance-engineer/SKILL.md +0 -319
  201. package/skills/eng-frontend-design-system/SKILL.md +0 -155
  202. package/skills/eng-frontend-design-system/references/1-estrutura-pacote.md +0 -57
  203. package/skills/eng-frontend-design-system/references/2-tokens.md +0 -116
  204. package/skills/eng-frontend-design-system/references/3-componentes.md +0 -165
  205. package/skills/eng-frontend-design-system/references/4-storybook.md +0 -85
  206. package/skills/eng-frontend-design-system/references/5-auditoria-versionamento.md +0 -76
  207. package/skills/eng-frontend-microfrontend/SKILL.md +0 -164
  208. package/skills/eng-frontend-microfrontend/references/1-criando-remote.md +0 -109
  209. package/skills/eng-frontend-microfrontend/references/2-configurando-shell.md +0 -70
  210. package/skills/eng-frontend-microfrontend/references/3-contratos-interface.md +0 -73
  211. package/skills/eng-frontend-microfrontend/references/4-shared-dependencies.md +0 -48
  212. package/skills/eng-frontend-microfrontend/references/5-event-bus.md +0 -59
  213. package/skills/eng-frontend-microfrontend/references/6-monorepo-deploy.md +0 -77
  214. package/skills/eng-frontend-microfrontend/references/7-troubleshooting.md +0 -45
  215. package/skills/eng-global-browser-extension-builder/SKILL.md +0 -392
  216. package/skills/eng-global-jira-comment/SKILL.md +0 -27
  217. package/skills/eng-qa-a11y-audit/SKILL.md +0 -276
  218. package/skills/eng-qa-bug-report/SKILL.md +0 -877
  219. package/skills/eng-qa-bug-report/TASK_MANAGERS.md +0 -138
  220. package/skills/eng-qa-bug-report/references/1-integracao-task-managers.md +0 -148
  221. package/skills/eng-qa-bug-report/references/2-exemplos-de-uso.md +0 -46
  222. package/skills/eng-qa-bug-report/references/3-bug-to-test.md +0 -45
  223. package/skills/eng-qa-cypress-e2e/SKILL.md +0 -184
  224. package/skills/eng-qa-dev-guide/SKILL.md +0 -173
  225. package/skills/eng-qa-e2e/SKILL.md +0 -407
  226. package/skills/eng-qa-e2e-spec-writer/SKILL.md +0 -329
  227. package/skills/eng-qa-exploratory/SKILL.md +0 -196
  228. package/skills/eng-qa-gate/SKILL.md +0 -377
  229. package/skills/eng-qa-gate/assets/checklist-validacao.md +0 -291
  230. package/skills/eng-qa-graphql-contract/SKILL.md +0 -263
  231. package/skills/eng-qa-quality-report/SKILL.md +0 -419
  232. package/skills/eng-qa-test-plan/SKILL.md +0 -473
  233. package/skills/eng-qa-testsprite/SKILL.md +0 -332
  234. package/skills/eng-qa-testsprite/references/testsprite-mcp.md +0 -224
  235. package/skills/eng-qa-unit-test/SKILL.md +0 -478
  236. package/skills/eng-scraper/SKILL.md +0 -260
  237. package/skills/eng-scraper/references/1-puppeteer.md +0 -140
  238. package/skills/eng-scraper/references/2-playwright-cheerio-pdf.md +0 -82
  239. package/skills/eng-scraper/references/3-anti-bot.md +0 -94
  240. package/skills/eng-scraper/references/4-resiliencia-etl-monitoramento.md +0 -137
  241. package/skills/eng-scraper-robot-builder/SKILL.md +0 -377
  242. package/skills/eng-security-cybersecurity/SKILL.md +0 -250
  243. package/skills/eng-security-cybersecurity/references/1-owasp-top-10.md +0 -331
  244. package/skills/eng-security-cybersecurity/references/2-secrets-management.md +0 -31
  245. package/skills/eng-security-cybersecurity/references/3-input-validation.md +0 -43
  246. package/skills/eng-security-cybersecurity/references/4-compliance.md +0 -20
  247. package/skills/eng-security-patch/SKILL.md +0 -388
  248. package/skills/eng-security-threat-model/SKILL.md +0 -171
  249. package/skills/eng-security-triage/SKILL.md +0 -276
  250. package/templates/engineering/qa/eng.qa.quality-gate-examples-template.md +0 -311
  251. package/templates/engineering/qa/eng.qa.quality-gate-report-template.md +0 -249
  252. package/templates/engineering/qa/qa.cypress-test-template.md +0 -172
  253. package/templates/engineering/qa/qa.exploratory-session-template.md +0 -148
  254. package/templates/engineering/qa/qa.quality-report-template.md +0 -130
  255. package/workflows/engineering/eng.security-audit.md +0 -319
  256. package/workflows/engineering/eng.security-incident.md +0 -281
  257. package/workflows/engineering/eng.security-pipeline.md +0 -219
  258. package/workflows/engineering/eng.security-review.md +0 -241
  259. package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +0 -57
  260. package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +0 -57
  261. package/workflows/engineering/qa/eng.qa-exploratory-session.md +0 -68
  262. package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +0 -209
  263. package/workflows/engineering/qa/eng.qa-quality-report.md +0 -85
  264. /package/skills/{eng-qa-test-plan → eng-qa-planner}/assets/test-coverage-template.md +0 -0
  265. /package/skills/{eng-qa-test-plan → eng-qa-planner}/assets/test-patterns.md +0 -0
  266. /package/templates/{CDD aplicado a Prompts.md → cdd-aplicado-a-prompts.md} +0 -0
@@ -1,27 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { getFrameworkRoot } from "../utils/paths.js";
4
-
5
- function parseFrontmatter(content) {
6
- const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
7
- if (!match) return {};
8
-
9
- const fm = {};
10
- for (const line of match[1].split("\n")) {
11
- const kv = line.match(/^(\w[\w-]*):\s*(.+)$/);
12
- if (kv) {
13
- let val = kv[2].trim();
14
- if (
15
- (val.startsWith('"') && val.endsWith('"')) ||
16
- (val.startsWith("'") && val.endsWith("'"))
17
- ) {
18
- val = val.slice(1, -1);
19
- }
20
- fm[kv[1]] = val;
21
- }
22
- }
23
- return fm;
24
- }
4
+ import { parseFrontmatter } from "../utils/frontmatter.js";
25
5
 
26
6
  function extractDeps(content) {
27
7
  const deps = { rules: [], templates: [], skills: [], agents: [] };
@@ -33,9 +13,7 @@ function extractDeps(content) {
33
13
  if (!deps.rules.includes(m[1])) deps.rules.push(m[1]);
34
14
  }
35
15
 
36
- const tplFile = content.match(
37
- /template_file:\s*"?\$IDE\/templates\/(.+?)\.md"?/,
38
- );
16
+ const tplFile = content.match(/template_file:\s*"?\$IDE\/templates\/(.+?)\.md"?/);
39
17
  if (tplFile) deps.templates.push(tplFile[1]);
40
18
 
41
19
  const agentFile = content.match(/agent:\s*"?\$IDE\/agents\/(.+?)\.md"?/);
@@ -75,11 +53,7 @@ function scanDir(dir, type) {
75
53
  continue;
76
54
  }
77
55
 
78
- if (
79
- !entry.name.endsWith(".md") ||
80
- entry.name === "README.md" ||
81
- entry.name === "AGENTS.md"
82
- ) {
56
+ if (!entry.name.endsWith(".md") || entry.name === "README.md" || entry.name === "AGENTS.md") {
83
57
  continue;
84
58
  }
85
59
 
@@ -12,6 +12,7 @@ import { execSync } from "node:child_process";
12
12
  import { join, relative, basename } from "node:path";
13
13
  import { getFrameworkRoot } from "../utils/paths.js";
14
14
  import { getIDEFolder } from "../config/ide-config.js";
15
+ import { extractFrontmatterBlock } from "../utils/frontmatter.js";
15
16
  import {
16
17
  SYNC_DIRS,
17
18
  SYNC_ROOT_FILES,
@@ -30,12 +31,11 @@ import {
30
31
  * @returns {{ name: string, description: string } | null}
31
32
  */
32
33
  function parseSkillFrontmatter(content) {
33
- const match = content.match(/^---\n([\s\S]*?)\n---/);
34
- if (!match) return null;
35
- const fm = match[1];
34
+ const fm = extractFrontmatterBlock(content);
35
+ if (fm === null) return null;
36
36
  const name = (fm.match(/^name:\s*(.+)$/m) || [])[1]?.trim();
37
37
  // description pode ser multiline com `>`
38
- const descMatch = fm.match(/^description:\s*[>|]?\n?([\s\S]*?)(?=\n\S|\n---|\Z)/m);
38
+ const descMatch = fm.match(/^description:\s*[>|]?\n?([\s\S]*?)(?=\n\S|\n---|Z)/m);
39
39
  const description = descMatch
40
40
  ? descMatch[1].replace(/^\s+/gm, "").replace(/\n+/g, " ").trim()
41
41
  : "";
@@ -59,8 +59,7 @@ function generateCodexOpenAIYaml(skillsDir) {
59
59
  const parsed = parseSkillFrontmatter(content);
60
60
  if (!parsed) continue;
61
61
  const yaml =
62
- `name: ${parsed.name}\n` +
63
- `description: "${parsed.description.replace(/"/g, '\\"')}"\n`;
62
+ `name: ${parsed.name}\n` + `description: "${parsed.description.replace(/"/g, '\\"')}"\n`;
64
63
  writeFileSync(yamlPath, yaml, "utf-8");
65
64
  }
66
65
  }
@@ -77,9 +76,7 @@ export function syncAssets(targetDir, ide, opts = {}) {
77
76
  for (const dir of SYNC_DIRS) {
78
77
  const src = join(frameworkRoot, dir);
79
78
  // Se for "workflows" e a IDE usa outro nome, usa o nome configurado
80
- const destDirName = (dir === "workflows" && opts.workflowsFolder)
81
- ? opts.workflowsFolder
82
- : dir;
79
+ const destDirName = dir === "workflows" && opts.workflowsFolder ? opts.workflowsFolder : dir;
83
80
  const dest = join(ideDir, destDirName);
84
81
 
85
82
  if (!existsSync(src)) {
@@ -246,7 +243,8 @@ function resolveOpenCodeModel(model) {
246
243
  if (OPENCODE_MODEL_MAP[trimmed]) return OPENCODE_MODEL_MAP[trimmed];
247
244
  // Inferir provider pelo prefixo do modelo
248
245
  if (trimmed.startsWith("claude")) return `anthropic/${trimmed}`;
249
- if (trimmed.startsWith("gpt") || trimmed.startsWith("o3") || trimmed.startsWith("o4")) return `openai/${trimmed}`;
246
+ if (trimmed.startsWith("gpt") || trimmed.startsWith("o3") || trimmed.startsWith("o4"))
247
+ return `openai/${trimmed}`;
250
248
  if (trimmed.startsWith("gemini")) return `google/${trimmed}`;
251
249
  // Fallback: retornar sem provider — OpenCode resolverá pelo provider padrão
252
250
  return trimmed;
@@ -370,11 +368,16 @@ function flattenDir(dir, rootDir) {
370
368
  const fullPath = join(dir, entry.name);
371
369
  if (entry.isDirectory()) {
372
370
  moved += flattenDir(fullPath, root);
373
- try { rmSync(fullPath, { recursive: true, force: true }); } catch {}
371
+ try {
372
+ rmSync(fullPath, { recursive: true, force: true });
373
+ } catch {}
374
374
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
375
375
  const dest = join(root, basename(entry.name));
376
376
  if (fullPath !== dest) {
377
- try { renameSync(fullPath, dest); moved++; } catch {}
377
+ try {
378
+ renameSync(fullPath, dest);
379
+ moved++;
380
+ } catch {}
378
381
  }
379
382
  }
380
383
  }
@@ -402,9 +405,13 @@ function toKiroSteeringFile(content, inclusion, fileMatchPattern) {
402
405
 
403
406
  if (fmMatch) {
404
407
  const fm = fmMatch[1];
405
- const description = (fm.match(/^description:\s*[>|]?\s*\n?([\s\S]*?)(?=\n\S|\n---|\Z)/m) || [])[1];
408
+ const description = (fm.match(/^description:\s*[>|]?\s*\n?([\s\S]*?)(?=\n\S|\n---|Z)/m) ||
409
+ [])[1];
406
410
  const descInline = (fm.match(/^description:\s*(.+)$/m) || [])[1];
407
- const finalDesc = (description || descInline || "").replace(/^\s+/gm, "").replace(/\n+/g, " ").trim();
411
+ const finalDesc = (description || descInline || "")
412
+ .replace(/^\s+/gm, "")
413
+ .replace(/\n+/g, " ")
414
+ .trim();
408
415
  if (finalDesc) lines.push(`description: "${finalDesc.replace(/"/g, '\\"')}"`);
409
416
  }
410
417
 
@@ -75,13 +75,23 @@ export function buildReport(root, profile, opts = {}) {
75
75
  const categories = [
76
76
  category("rules", "Rules", rules, "carregadas no início pelo Claude Code"),
77
77
  category("agents-md", "AGENTS.md", agentsMd, "instrução de projeto"),
78
- category("skills-frontmatter", "Frontmatter dos skills", skills, "sempre visível; o corpo só ao invocar"),
78
+ category(
79
+ "skills-frontmatter",
80
+ "Frontmatter dos skills",
81
+ skills,
82
+ "sempre visível; o corpo só ao invocar"
83
+ ),
79
84
  category("warmup", "warm-up", warmup, "só se executado no início da sessão"),
80
85
  ];
81
86
 
82
87
  const bytes = categories.reduce((sum, c) => sum + c.bytes, 0);
83
88
  return {
84
- profile: { HUB: profile.HUB, POSITION: profile.POSITION, AREA: profile.AREA, SQUAD: profile.SQUAD },
89
+ profile: {
90
+ HUB: profile.HUB,
91
+ POSITION: profile.POSITION,
92
+ AREA: profile.AREA,
93
+ SQUAD: profile.SQUAD,
94
+ },
85
95
  rtkEnabled,
86
96
  estimate: `tokens = bytes ÷ ${BYTES_PER_TOKEN} (estimativa)`,
87
97
  categories,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @fileoverview Busca um arquivo do central-docs (GitLab/GitHub/Bitbucket), com
3
+ * cache Redis best-effort. Substitui `fetch-file.sh` (removido) — mesmo
4
+ * comportamento, sem subprocess: chama `fetchRawFile` de `vcs/api.js` direto.
5
+ * @module docs/fetch-file
6
+ */
7
+
8
+ import { fetchRawFile } from "../vcs/api.js";
9
+ import { redisGet, redisSetex } from "./redis-cache.js";
10
+
11
+ /**
12
+ * @param {string} filePath - Path do arquivo dentro do repo central-docs
13
+ * @param {string} [ref] - Branch/ref (padrão: "main")
14
+ * @param {Object} [opts]
15
+ * @param {string} [opts.repo] - CENTRAL_DOCS_REPO (padrão: `process.env.CENTRAL_DOCS_REPO`)
16
+ * @param {string|number} [opts.cacheTtl] - TTL do cache em segundos (padrão: `process.env.CENTRAL_DOCS_CACHE_TTL` ou 3600)
17
+ * @returns {Promise<string>} conteúdo do arquivo
18
+ * @throws {Error} sem `filePath`, sem `repo`, ou erro do adapter (404/401/403 — ver `fetchRawFile`)
19
+ */
20
+ export async function fetchFile(filePath, ref = "main", opts = {}) {
21
+ if (!filePath) {
22
+ throw new Error("fetchFile: file-path é obrigatório");
23
+ }
24
+
25
+ const repo = opts.repo ?? process.env.CENTRAL_DOCS_REPO;
26
+ if (!repo) {
27
+ throw new Error("CENTRAL_DOCS_REPO não definido no ENV.md");
28
+ }
29
+
30
+ const cacheKey = `docs:${filePath}:${ref}`;
31
+ const cached = redisGet(cacheKey);
32
+ if (cached) return cached;
33
+
34
+ const content = await fetchRawFile(repo, filePath, ref);
35
+
36
+ redisSetex(cacheKey, opts.cacheTtl ?? process.env.CENTRAL_DOCS_CACHE_TTL ?? "3600", content);
37
+
38
+ return content;
39
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @fileoverview Publica um documento no central-docs (branch + commit + MR/PR).
3
+ * Substitui `publish-file.sh` (removido) — chama os adapters de `vcs/api.js`
4
+ * direto, sem subprocess, sem arquivo temporário para passar JSON/body.
5
+ * @module docs/publish-file
6
+ */
7
+
8
+ import { readFileSync } from "node:fs";
9
+ import { validateFrontmatter, extractMetadata } from "./validate-frontmatter.js";
10
+ import { commitFiles, createMergeRequest } from "../vcs/api.js";
11
+ import { fetchFile } from "./fetch-file.js";
12
+ import { redisDel } from "./redis-cache.js";
13
+ import { logger } from "../utils/logger.js";
14
+
15
+ const VALID_TYPES = ["prd", "frd", "ard", "rfc", "swagger", "qa-report"];
16
+
17
+ /**
18
+ * @param {{ tipo: string, squad: string, workspace: string, feature: string, localFile: string }} p
19
+ * @returns {{ destPath: string, area: "product"|"engineering" }}
20
+ */
21
+ export function resolveDestPath({ tipo, squad, workspace, feature, localFile }) {
22
+ const base = `${squad}/${workspace}`;
23
+
24
+ if (tipo === "prd" || tipo === "frd") {
25
+ return { destPath: `${base}/product/${tipo}-${feature}.md`, area: "product" };
26
+ }
27
+ if (tipo === "ard") {
28
+ return { destPath: `${base}/engineering/ARD/${tipo}-${feature}.md`, area: "engineering" };
29
+ }
30
+ if (tipo === "rfc") {
31
+ return { destPath: `${base}/engineering/RFC/${tipo}-${feature}.md`, area: "engineering" };
32
+ }
33
+ if (tipo === "swagger") {
34
+ const ext = localFile.split(".").pop().toLowerCase();
35
+ const swaggerExt = ext === "yaml" || ext === "yml" ? ext : ext === "json" ? "json" : null;
36
+ const destPath = swaggerExt
37
+ ? `${base}/engineering/swagger/api-${feature}.${swaggerExt}`
38
+ : `${base}/engineering/swagger/swagger-${feature}.md`; // default: .md (wrapper com metadados)
39
+ return { destPath, area: "engineering" };
40
+ }
41
+ if (tipo === "qa-report") {
42
+ return { destPath: `${base}/engineering/qa/qa-report-${feature}.md`, area: "engineering" };
43
+ }
44
+
45
+ throw new Error(`Tipo inválido: ${tipo}\nTipos válidos: ${VALID_TYPES.join(", ")}`);
46
+ }
47
+
48
+ /**
49
+ * Insere `newLine` logo após o header `## {area}` — sem nunca descartar uma
50
+ * linha existente. O `publish-file.sh` original usava `getline` do AWK para
51
+ * "pular" a linha seguinte ao header antes de imprimir a nova entrada; essa
52
+ * linha pulada nunca era reimpressa, o que apagava silenciosamente a entrada
53
+ * anterior do índice na segunda publicação numa mesma seção (reproduzido e
54
+ * documentado em `architecture.md` desta sessão, seção 3.4).
55
+ * @param {string} content
56
+ * @param {"product"|"engineering"} area
57
+ * @param {string} newLine
58
+ * @returns {{ content: string, found: boolean }}
59
+ */
60
+ export function insertIndexLine(content, area, newLine) {
61
+ const lines = content.split("\n");
62
+ const headerPattern = /^## (product|engineering)\b/;
63
+
64
+ for (let i = 0; i < lines.length; i++) {
65
+ const match = lines[i].match(headerPattern);
66
+ if (match && match[1] === area) {
67
+ lines.splice(i + 1, 0, newLine);
68
+ return { content: lines.join("\n"), found: true };
69
+ }
70
+ }
71
+
72
+ const trimmed = content.replace(/\n+$/, "");
73
+ return { content: `${trimmed}\n\n## ${area}\n${newLine}\n`, found: false };
74
+ }
75
+
76
+ /**
77
+ * @param {Object} opts
78
+ * @param {string} opts.localFile - Caminho do arquivo local (ex: `./docs/engineering/ard-x.md`)
79
+ * @param {string} opts.tipo - prd|frd|ard|rfc|swagger|qa-report
80
+ * @param {string} opts.feature - Slug da feature (kebab-case)
81
+ * @param {string} opts.squad - Squad (já resolvido pelo caller: override ou ENV.md)
82
+ * @param {string} opts.workspace - Workspace (já resolvido pelo caller: override ou ENV.md)
83
+ * @param {string} opts.repo - CENTRAL_DOCS_REPO
84
+ * @param {string} [opts.targetBranch] - Branch base do MR/PR (padrão: "dev")
85
+ * @param {string|number} [opts.cacheTtl] - TTL do cache do index.md em segundos (padrão: 3600)
86
+ * @returns {Promise<{ sha: string, mr: { vendor: string, iid: number|string, url: string } }>}
87
+ */
88
+ export async function publishFile(opts) {
89
+ const {
90
+ localFile,
91
+ tipo,
92
+ feature,
93
+ squad,
94
+ workspace,
95
+ repo,
96
+ targetBranch = "dev",
97
+ cacheTtl = "3600",
98
+ } = opts;
99
+
100
+ if (!repo) {
101
+ throw new Error(
102
+ "CENTRAL_DOCS_REPO não configurado no ENV.md\n\nConfigure a URL do repositório central:\n CENTRAL_DOCS_REPO=https://gitlab.com/org/central-docs.git"
103
+ );
104
+ }
105
+
106
+ logger.info("Validando frontmatter...");
107
+ validateFrontmatter(localFile, tipo);
108
+ logger.info("✅ Frontmatter válido");
109
+
110
+ const metadata = extractMetadata(localFile);
111
+ const version = metadata.version || "1.0.0";
112
+ const status = metadata.status || "draft";
113
+ const jira = metadata.jira || "";
114
+ const docName = metadata.name || "";
115
+
116
+ const { destPath, area } = resolveDestPath({ tipo, squad, workspace, feature, localFile });
117
+ const indexPath = `${squad}/${workspace}/index.md`;
118
+ const branchName = `docs/${squad}/${workspace}/${tipo}-${feature}`;
119
+
120
+ logger.info("");
121
+ logger.info("📋 Resumo da Publicação:");
122
+ logger.info(` Squad: ${squad}`);
123
+ logger.info(` Workspace: ${workspace}`);
124
+ logger.info(` Tipo: ${tipo}`);
125
+ logger.info(` Feature: ${feature}`);
126
+ logger.info(` Destino: ${destPath}`);
127
+ logger.info(` Branch: ${branchName} → ${targetBranch}`);
128
+ logger.info("");
129
+
130
+ const fileContentBase64 = readFileSync(localFile).toString("base64");
131
+
132
+ logger.info("Buscando index.md do workspace...");
133
+ let indexContent = "";
134
+ try {
135
+ indexContent = await fetchFile(indexPath, targetBranch, { repo, cacheTtl });
136
+ } catch {
137
+ indexContent = "";
138
+ }
139
+
140
+ let indexAction;
141
+ if (!indexContent) {
142
+ logger.info("⚠️ index.md não encontrado, será criado");
143
+ indexAction = "create";
144
+ indexContent = `# ${workspace}\n\n**Squad:** ${squad}\n\n## ${area}\n\n`;
145
+ } else {
146
+ indexAction = "update";
147
+ }
148
+
149
+ let newLine = `- [${tipo}-${feature}.md](${area}/${tipo}-${feature}.md) | v${version} | ${status}`;
150
+ if (jira) newLine += ` | jira: ${jira}`;
151
+ if (docName) newLine += ` | ${docName}`;
152
+
153
+ const { content: updatedIndex } = insertIndexLine(indexContent, area, newLine);
154
+ const updatedIndexBase64 = Buffer.from(updatedIndex, "utf-8").toString("base64");
155
+
156
+ logger.info("Criando branch e commit...");
157
+ const commitResult = await commitFiles(repo, {
158
+ branch: branchName,
159
+ startBranch: targetBranch,
160
+ message: `docs(${squad}): add ${tipo} ${feature}`,
161
+ files: [
162
+ { path: destPath, contentBase64: fileContentBase64, action: "create" },
163
+ { path: indexPath, contentBase64: updatedIndexBase64, action: indexAction },
164
+ ],
165
+ });
166
+ logger.info(`✅ Commit criado: ${commitResult.sha}`);
167
+
168
+ logger.info("Criando Merge/Pull Request...");
169
+ let mrDescription = `## Documento\n\n- **Tipo**: ${tipo}\n- **Workspace**: ${workspace}\n- **Feature**: ${feature}`;
170
+ if (jira) mrDescription += `\n- **Card**: ${jira}`;
171
+ if (docName) mrDescription += `\n- **Nome**: ${docName}`;
172
+
173
+ const mr = await createMergeRequest(repo, {
174
+ source: branchName,
175
+ target: targetBranch,
176
+ title: `docs(${squad}): ${tipo} ${feature}`,
177
+ description: mrDescription,
178
+ });
179
+
180
+ logger.info("");
181
+ logger.info("✅ Merge/Pull Request criado com sucesso!");
182
+ logger.info("");
183
+ logger.info(` #${mr.iid}: ${branchName} → ${targetBranch}`);
184
+ logger.info(` URL: ${mr.url}`);
185
+ logger.info("");
186
+
187
+ redisDel(`docs:${indexPath}:${targetBranch}`);
188
+ redisDel(`docs:${indexPath}:main`);
189
+ logger.info("🔄 Cache Redis invalidado");
190
+ logger.info("");
191
+ logger.info("✅ Publicação concluída!");
192
+
193
+ return { sha: commitResult.sha, mr };
194
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Cache best-effort via `redis-cli` para fetch-file.js/publish-file.js.
3
+ * Nunca lança — falha de leitura/escrita no cache nunca quebra o fluxo principal
4
+ * (mesmo contrato do `fetch-file.sh`/`publish-file.sh` originais, que faziam
5
+ * `redis-cli ... 2>/dev/null || true`).
6
+ * @module docs/redis-cache
7
+ */
8
+
9
+ import { execFileSync } from "node:child_process";
10
+
11
+ /**
12
+ * @param {string} key
13
+ * @returns {string} valor em cache, ou "" se não houver (ou `redis-cli` falhar)
14
+ */
15
+ export function redisGet(key) {
16
+ try {
17
+ return execFileSync("redis-cli", ["GET", key], {
18
+ encoding: "utf-8",
19
+ stdio: ["ignore", "pipe", "ignore"],
20
+ }).trim();
21
+ } catch {
22
+ return "";
23
+ }
24
+ }
25
+
26
+ /**
27
+ * @param {string} key
28
+ * @param {string|number} ttlSeconds
29
+ * @param {string} value
30
+ */
31
+ export function redisSetex(key, ttlSeconds, value) {
32
+ try {
33
+ execFileSync("redis-cli", ["-x", "SETEX", key, String(ttlSeconds)], {
34
+ input: value,
35
+ stdio: ["pipe", "ignore", "ignore"],
36
+ });
37
+ } catch {
38
+ // best-effort
39
+ }
40
+ }
41
+
42
+ /** @param {string} key */
43
+ export function redisDel(key) {
44
+ try {
45
+ execFileSync("redis-cli", ["DEL", key], { stdio: "ignore" });
46
+ } catch {
47
+ // best-effort
48
+ }
49
+ }
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs'
2
+ import { parseFrontmatter, extractFrontmatterBlock } from '../utils/frontmatter.js'
2
3
 
3
4
  /**
4
5
  * Valida frontmatter YAML de documentos (PRD, FRD, ARD, RFC)
@@ -10,11 +11,10 @@ import { readFileSync } from 'node:fs'
10
11
  */
11
12
  export function validateFrontmatter(filePath, tipo) {
12
13
  const content = readFileSync(filePath, 'utf-8')
13
-
14
- // Extrair frontmatter YAML (entre --- e ---)
15
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
16
-
17
- if (!frontmatterMatch) {
14
+
15
+ const frontmatter = extractFrontmatterBlock(content)
16
+
17
+ if (frontmatter === null) {
18
18
  throw new Error(
19
19
  `Frontmatter YAML não encontrado em ${filePath}\n` +
20
20
  'Formato esperado:\n' +
@@ -25,8 +25,7 @@ export function validateFrontmatter(filePath, tipo) {
25
25
  )
26
26
  }
27
27
 
28
- const frontmatter = frontmatterMatch[1]
29
- const metadata = parseFrontmatter(frontmatter)
28
+ const metadata = parseFrontmatter(content)
30
29
 
31
30
  // Validar campos obrigatórios por tipo
32
31
  const requiredFields = getRequiredFields(tipo)
@@ -67,34 +66,6 @@ export function validateFrontmatter(filePath, tipo) {
67
66
  return metadata
68
67
  }
69
68
 
70
- /**
71
- * Parseia frontmatter YAML simples (key: value)
72
- *
73
- * @param {string} frontmatter - Conteúdo do frontmatter
74
- * @returns {Object} Metadados parseados
75
- */
76
- function parseFrontmatter(frontmatter) {
77
- const metadata = {}
78
- const lines = frontmatter.split('\n')
79
-
80
- for (const line of lines) {
81
- const trimmed = line.trim()
82
- if (!trimmed || trimmed.startsWith('#')) continue
83
-
84
- const colonIndex = trimmed.indexOf(':')
85
- if (colonIndex === -1) continue
86
-
87
- const key = trimmed.slice(0, colonIndex).trim()
88
- const value = trimmed.slice(colonIndex + 1).trim()
89
-
90
- if (key && value) {
91
- metadata[key] = value
92
- }
93
- }
94
-
95
- return metadata
96
- }
97
-
98
69
  /**
99
70
  * Retorna campos obrigatórios por tipo de documento
100
71
  *
@@ -121,13 +92,12 @@ function getRequiredFields(tipo) {
121
92
  */
122
93
  export function extractMetadata(filePath) {
123
94
  const content = readFileSync(filePath, 'utf-8')
124
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
125
-
126
- if (!frontmatterMatch) {
95
+
96
+ if (extractFrontmatterBlock(content) === null) {
127
97
  return {}
128
98
  }
129
99
 
130
- const metadata = parseFrontmatter(frontmatterMatch[1])
100
+ const metadata = parseFrontmatter(content)
131
101
 
132
102
  // Normalizar campos (suportar variações de nomenclatura)
133
103
  const taskLink = metadata.task_link || metadata.jira
@@ -2,25 +2,7 @@ import { readFileSync, existsSync } from "node:fs";
2
2
 
3
3
  import { join, isAbsolute, dirname, basename } from "node:path";
4
4
 
5
- const IDE_DIRS = {
6
- windsurf: ".windsurf",
7
- claude: ".claude",
8
- cursor: ".cursor",
9
- vscode: ".vscode",
10
- codex: ".codex",
11
- opencode: ".opencode",
12
- gemini: ".gemini",
13
- };
14
-
15
- const AUTO_DETECT_ORDER = [
16
- "windsurf",
17
- "claude",
18
- "cursor",
19
- "vscode",
20
- "codex",
21
- "opencode",
22
- "gemini",
23
- ];
5
+ import { getIDEFolder, getIDEValues, isIDESupported } from "./config/ide-config.js";
24
6
 
25
7
  function* walkAncestors(start) {
26
8
  let dir = start;
@@ -33,12 +15,11 @@ function* walkAncestors(start) {
33
15
  }
34
16
 
35
17
  function envsAt(dir, ideFilter = null) {
36
- const ides = ideFilter ? [ideFilter] : AUTO_DETECT_ORDER;
18
+ const ides = ideFilter ? [ideFilter] : getIDEValues();
37
19
  const found = [];
38
20
  for (const ide of ides) {
39
- const folder = IDE_DIRS[ide];
40
- if (!folder) continue;
41
- const p = join(dir, folder, "ENV.md");
21
+ if (!isIDESupported(ide)) continue;
22
+ const p = join(dir, `.${getIDEFolder(ide)}`, "ENV.md");
42
23
  if (existsSync(p)) found.push({ ide, path: p, root: dir });
43
24
  }
44
25
  return found;
@@ -65,13 +46,10 @@ export function deriveWorkspaceName(envPath) {
65
46
  export function resolveEnvPath(cwd, flags = {}) {
66
47
  // 1. --env-file
67
48
  if (flags["env-file"]) {
68
- const p = isAbsolute(flags["env-file"])
69
- ? flags["env-file"]
70
- : join(cwd, flags["env-file"]);
49
+ const p = isAbsolute(flags["env-file"]) ? flags["env-file"] : join(cwd, flags["env-file"]);
71
50
  if (!existsSync(p)) {
72
51
  throw new Error(
73
- `ENV.md não encontrado: ${p}\n` +
74
- `Dica: verifique o path passado em --env-file`,
52
+ `ENV.md não encontrado: ${p}\n` + `Dica: verifique o path passado em --env-file`
75
53
  );
76
54
  }
77
55
  return { path: p, source: "--env-file", warnings: [], root: dirname(dirname(p)) };
@@ -79,10 +57,9 @@ export function resolveEnvPath(cwd, flags = {}) {
79
57
 
80
58
  // 2. --ide (cwd e ancestrais — abre um repo filho e ainda acha o workspace)
81
59
  if (flags.ide) {
82
- if (!IDE_DIRS[flags.ide]) {
60
+ if (!isIDESupported(flags.ide)) {
83
61
  throw new Error(
84
- `IDE não suportada: "${flags.ide}"\n` +
85
- `IDEs válidas: ${Object.keys(IDE_DIRS).join(", ")}`,
62
+ `IDE não suportada: "${flags.ide}"\n` + `IDEs válidas: ${getIDEValues().join(", ")}`
86
63
  );
87
64
  }
88
65
  for (const dir of walkAncestors(cwd)) {
@@ -98,7 +75,7 @@ export function resolveEnvPath(cwd, flags = {}) {
98
75
  }
99
76
  throw new Error(
100
77
  `ENV.md não encontrado para --ide ${flags.ide} a partir de ${cwd}\n` +
101
- `Dica: rode jarvis init --ide ${flags.ide} na pasta do workspace`,
78
+ `Dica: rode jarvis init --ide ${flags.ide} na pasta do workspace`
102
79
  );
103
80
  }
104
81
 
@@ -114,7 +91,7 @@ export function resolveEnvPath(cwd, flags = {}) {
114
91
  }
115
92
 
116
93
  // 4. IDE env var
117
- if (process.env.IDE && IDE_DIRS[process.env.IDE]) {
94
+ if (process.env.IDE && isIDESupported(process.env.IDE)) {
118
95
  for (const dir of walkAncestors(cwd)) {
119
96
  const found = envsAt(dir, process.env.IDE);
120
97
  if (found.length === 1) {
@@ -135,7 +112,7 @@ export function resolveEnvPath(cwd, flags = {}) {
135
112
  const ides = found.map((f) => f.ide).join(" | ");
136
113
  throw new Error(
137
114
  `Múltiplos ENV.md encontrados em ${dir} (${ides}).\n` +
138
- `Especifique a IDE ativa. Exemplo: --ide claude`,
115
+ `Especifique a IDE ativa. Exemplo: --ide claude`
139
116
  );
140
117
  }
141
118
  if (found.length === 1) {
@@ -99,9 +99,11 @@ export function classifyCell(cell) {
99
99
  const token = span[1].trim();
100
100
  if (span[2]) targets.push({ kind: "agent", name: token });
101
101
  else if (token.startsWith("/")) {
102
- targets.push({ kind: external ? "external" : "internal", name: token.slice(1).split(/\s+/)[0] });
103
- }
104
- else if (/^(jarvis|node)\s/.test(token)) targets.push({ kind: "cli", name: token });
102
+ targets.push({
103
+ kind: external ? "external" : "internal",
104
+ name: token.slice(1).split(/\s+/)[0],
105
+ });
106
+ } else if (/^(jarvis|node)\s/.test(token)) targets.push({ kind: "cli", name: token });
105
107
  else if (token.startsWith("mcp__")) targets.push({ kind: "mcp", name: token });
106
108
  // Read, Grep, Glob e texto livre não são chamadas a artefatos: ignorados
107
109
  }
@@ -140,7 +142,9 @@ function parseTable(text, title) {
140
142
  }
141
143
 
142
144
  function section(text, title) {
143
- const m = text.match(new RegExp(`^## ${title}\\s*\\n([\\s\\S]*?)(?=\\n## |\\n---\\s*\\n|(?![\\s\\S]))`, "m"));
145
+ const m = text.match(
146
+ new RegExp(`^## ${title}\\s*\\n([\\s\\S]*?)(?=\\n## |\\n---\\s*\\n|(?![\\s\\S]))`, "m")
147
+ );
144
148
  return m ? m[1] : null;
145
149
  }
146
150
 
@@ -153,7 +157,9 @@ function parseAgent(text) {
153
157
  for (const block of skills.split(/^### /m).slice(1)) {
154
158
  const [heading, ...rest] = block.split("\n");
155
159
  // Um bloco pode citar mais de uma skill: uma linha por skill (nome tirado do caminho do SKILL.md).
156
- const names = [...new Set([...block.matchAll(/skills\/([A-Za-z0-9._-]+)\/SKILL\.md/g)].map((m) => m[1]))];
160
+ const names = [
161
+ ...new Set([...block.matchAll(/skills\/([A-Za-z0-9._-]+)\/SKILL\.md/g)].map((m) => m[1])),
162
+ ];
157
163
  if (names.length === 0) {
158
164
  const token = heading.trim().split(/[\s(]/)[0];
159
165
  if (/[-.]/.test(token)) names.push(token);