jarvis-ai-framework 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/AGENTS.md +22 -122
  2. package/agents/AGENTS.md +20 -24
  3. package/agents/README.md +25 -129
  4. package/agents/engineering/data/eng.data-engineer.agent.md +12 -35
  5. package/agents/engineering/eng.agent.md +18 -30
  6. package/agents/engineering/eng.bug-hunter.md +9 -12
  7. package/agents/engineering/eng.dev-code-reviewer.md +5 -1
  8. package/agents/engineering/eng.docs-writer.md +3 -3
  9. package/agents/engineering/eng.frontend.agent.md +13 -8
  10. package/agents/engineering/eng.rpa.agent.md +5 -13
  11. package/agents/engineering/eng.ux-designer.agent.md +4 -0
  12. package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +4 -0
  13. package/agents/engineering/qa/eng.qa.quality-strategist.md +13 -8
  14. package/agents/engineering/qa/eng.qa.test-architect.md +16 -15
  15. package/agents/engineering/qa/eng.qa.test-planner.md +22 -17
  16. package/agents/engineering/qa/eng.qa.testing-engineer.md +99 -15
  17. package/agents/product/prod.discovery-interviewer.md +22 -3
  18. package/agents/product/prod.pm-checker.md +4 -0
  19. package/bin/README.md +142 -0
  20. package/bin/commands/docs-publish.js +105 -141
  21. package/bin/commands/docs-sync.js +66 -104
  22. package/bin/commands/info.js +7 -27
  23. package/bin/commands/init.js +23 -51
  24. package/bin/commands/install-rtk.js +9 -3
  25. package/bin/commands/list.js +5 -18
  26. package/bin/commands/map.js +58 -0
  27. package/bin/commands/qa-signoff.js +58 -55
  28. package/bin/commands/tokens.js +90 -0
  29. package/bin/commands/whoami.js +30 -26
  30. package/bin/jarvis.js +9 -153
  31. package/bin/lib/auth/session.js +20 -20
  32. package/bin/lib/cli/program.js +219 -0
  33. package/bin/lib/cli/shared-options.js +29 -0
  34. package/bin/lib/config/constants.js +70 -2
  35. package/bin/lib/config/ide-config.js +4 -8
  36. package/bin/lib/core/profile-filter.js +116 -0
  37. package/bin/lib/core/scanner.js +5 -31
  38. package/bin/lib/core/sync-engine.js +21 -14
  39. package/bin/lib/core/token-report.js +100 -0
  40. package/bin/lib/docs/fetch-file.js +39 -0
  41. package/bin/lib/docs/publish-file.js +194 -0
  42. package/bin/lib/docs/redis-cache.js +49 -0
  43. package/bin/lib/docs/validate-frontmatter.js +9 -39
  44. package/bin/lib/env-loader.js +11 -34
  45. package/bin/lib/flow-map/index.js +65 -0
  46. package/bin/lib/flow-map/parse.js +223 -0
  47. package/bin/lib/flow-map/render.js +89 -0
  48. package/bin/lib/flow-map/tree.js +248 -0
  49. package/bin/lib/tasks/comment.js +40 -35
  50. package/bin/lib/utils/frontmatter.js +57 -0
  51. package/bin/lib/utils/git-parser.js +4 -6
  52. package/bin/lib/utils/npmrc-parser.js +6 -13
  53. package/bin/lib/utils/paths.js +2 -4
  54. package/bin/lib/utils/ui.js +3 -9
  55. package/bin/lib/vcs/api.js +25 -41
  56. package/bin/lib/vcs/create-issue.js +49 -33
  57. package/bin/lib/vcs/create-merge.js +47 -31
  58. package/bin/lib/vcs/fetch-raw.js +35 -23
  59. package/members.md +1 -1
  60. package/package.json +15 -4
  61. package/rules/AGENTS.md +18 -7
  62. package/rules/engineering/eng-rules.md +3 -13
  63. package/rules/engineering/eng-security-rules.md +19 -18
  64. package/rules/engineering/eng.bump-rules.md +3 -2
  65. package/rules/engineering/eng.docs-scraping-rules.md +4 -4
  66. package/rules/engineering/eng.integrations-rules.md +3 -7
  67. package/rules/engineering/eng.skills-rules.md +1 -5
  68. package/rules/engineering/eng.specializations-rules.md +18 -8
  69. package/rules/engineering/frontend/eng.frontend-rules.md +1 -4
  70. package/rules/engineering/qa/eng.qa.cypress-standards-rules.md +1 -1
  71. package/rules/engineering/rpa/eng.rpa-rules.md +4 -9
  72. package/rules/product/README.md +1 -0
  73. package/rules/product/prod-rules.md +1 -4
  74. package/rules/product/prod.spec-rules.md +0 -4
  75. package/rules/rtk-rules.md +0 -1
  76. package/{rules → rules-on-demand}/engineering/eng.breakdown-subtasks-rules.md +0 -4
  77. package/{rules → rules-on-demand}/engineering/eng.plan-rules.md +0 -4
  78. package/{rules → rules-on-demand}/engineering/eng.pr-rules.md +0 -4
  79. package/{rules → rules-on-demand}/engineering/eng.pre-pr-rules.md +0 -4
  80. package/{rules → rules-on-demand}/engineering/eng.start-rules.md +0 -4
  81. package/{rules → rules-on-demand}/engineering/eng.tech-spec-rules.md +1 -2
  82. package/{rules → rules-on-demand}/engineering/eng.work-rules.md +0 -4
  83. package/skills/AGENTS.md +69 -106
  84. package/skills/SKILLS-ROADMAP.md +47 -132
  85. package/skills/eng-ai/SKILL.md +270 -0
  86. package/skills/eng-ai/references/1-fundamentos-ml.md +24 -0
  87. package/skills/eng-ai/references/10-arquitetura-solucoes-ia.md +24 -0
  88. package/skills/eng-ai/references/11-governanca-ia.md +24 -0
  89. package/skills/eng-ai/references/12-seguranca-ia-responsavel.md +25 -0
  90. package/skills/eng-ai/references/2-matematica-estatistica.md +24 -0
  91. package/skills/eng-ai/references/3-dados-para-ia.md +24 -0
  92. package/skills/eng-ai/references/4-deep-learning-fundacionais.md +24 -0
  93. package/skills/eng-ai/references/5-llms-chatbots-agentes-rag.md +25 -0
  94. package/skills/eng-ai/references/6-avaliacao-experimentacao.md +24 -0
  95. package/skills/eng-ai/references/7-engenharia-sistemas-ia.md +24 -0
  96. package/skills/eng-ai/references/8-mlops-plataforma.md +24 -0
  97. package/skills/eng-ai/references/9-produto-ia.md +24 -0
  98. package/skills/eng-ai/references/especializacoes.md +17 -0
  99. package/skills/eng-automation/SKILL.md +290 -0
  100. package/skills/eng-automation/references/1-fundamentos-automacao.md +25 -0
  101. package/skills/eng-automation/references/10-ingestao-dados.md +25 -0
  102. package/skills/eng-automation/references/11-captcha-antibot.md +25 -0
  103. package/skills/eng-automation/references/12-limites-taxa-sessoes.md +25 -0
  104. package/skills/eng-automation/references/13-agendamento-orquestracao.md +24 -0
  105. package/skills/eng-automation/references/14-retentativa-erros.md +25 -0
  106. package/skills/eng-automation/references/15-autenticacao-credenciais.md +24 -0
  107. package/skills/eng-automation/references/16-observabilidade-confiabilidade.md +24 -0
  108. package/skills/eng-automation/references/2-aquisicao-dados.md +25 -0
  109. package/skills/eng-automation/references/3-automacao-navegador.md +24 -0
  110. package/skills/eng-automation/references/4-scraping-crawling.md +25 -0
  111. package/skills/eng-automation/references/5-apis-rest-graphql-soap.md +26 -0
  112. package/skills/eng-automation/references/6-webhooks-integracao.md +25 -0
  113. package/skills/eng-automation/references/7-rpa.md +25 -0
  114. package/skills/eng-automation/references/8-extracao-estruturada.md +25 -0
  115. package/skills/eng-automation/references/9-extracao-documentos.md +25 -0
  116. package/skills/eng-automation/references/especializacoes.md +18 -0
  117. package/skills/eng-backend/SKILL.md +6 -0
  118. package/skills/eng-backend-arch-c4/SKILL.md +10 -4
  119. package/skills/eng-backend-microservices-trace/SKILL.md +6 -0
  120. package/skills/eng-backend-nestjs/SKILL.md +17 -462
  121. package/skills/eng-backend-nestjs/references/1-arquitetura-modulos.md +54 -0
  122. package/skills/eng-backend-nestjs/references/2-guards-autenticacao.md +118 -0
  123. package/skills/eng-backend-nestjs/references/3-interceptors-pipes-filters.md +113 -0
  124. package/skills/eng-backend-nestjs/references/4-config-logging.md +61 -0
  125. package/skills/eng-backend-nestjs/references/5-testes.md +83 -0
  126. package/skills/eng-backend-nestjs/references/6-problemas-comuns.md +53 -0
  127. package/skills/eng-backend-rabbitmq/SKILL.md +15 -298
  128. package/skills/eng-backend-rabbitmq/references/1-referencia-endpoints.md +28 -0
  129. package/skills/eng-backend-rabbitmq/references/2-codigo-consumer-producer.md +90 -0
  130. package/skills/eng-backend-rabbitmq/references/3-arquiteturas-comuns.md +101 -0
  131. package/skills/eng-backend-rabbitmq/references/4-troubleshooting.md +93 -0
  132. package/skills/eng-data/SKILL.md +271 -0
  133. package/skills/eng-data/references/1-fundamentos-dados.md +24 -0
  134. package/skills/eng-data/references/10-ciencia-dados.md +24 -0
  135. package/skills/eng-data/references/11-dataops-plataforma.md +24 -0
  136. package/skills/eng-data/references/12-seguranca-privacidade.md +24 -0
  137. package/skills/eng-data/references/2-modelagem-dados.md +24 -0
  138. package/skills/eng-data/references/3-bancos-dados.md +24 -0
  139. package/skills/eng-data/references/4-arquitetura-dados.md +24 -0
  140. package/skills/eng-data/references/5-pipelines-integracao.md +24 -0
  141. package/skills/eng-data/references/6-qualidade-dados.md +24 -0
  142. package/skills/eng-data/references/7-governanca-metadados.md +24 -0
  143. package/skills/eng-data/references/8-estatistica-analise.md +24 -0
  144. package/skills/eng-data/references/9-analytics-bi.md +24 -0
  145. package/skills/eng-data/references/especializacoes.md +20 -0
  146. package/skills/eng-frontend/SKILL.md +18 -5
  147. package/skills/eng-frontend/references/13-design-system.md +48 -0
  148. package/skills/eng-frontend/references/14-microfrontend.md +46 -0
  149. package/skills/eng-global-docs-write/SKILL.md +10 -5
  150. package/skills/eng-global-pr/SKILL.md +7 -1
  151. package/skills/eng-global-task-comment/SKILL.md +8 -2
  152. package/skills/eng-platform/SKILL.md +331 -0
  153. package/skills/eng-platform/references/1-fundamentos-sistemas.md +23 -0
  154. package/skills/eng-platform/references/10-resiliencia-arquitetura.md +24 -0
  155. package/skills/eng-platform/references/11-plataforma-devex.md +24 -0
  156. package/skills/eng-platform/references/12-finops-custos.md +24 -0
  157. package/skills/eng-platform/references/13-seguranca-fundamentos.md +26 -0
  158. package/skills/eng-platform/references/14-profiling-performance.md +23 -0
  159. package/skills/eng-platform/references/15-testes-carga-chaos.md +23 -0
  160. package/skills/eng-platform/references/16-cache-multicamada.md +22 -0
  161. package/skills/eng-platform/references/2-redes.md +23 -0
  162. package/skills/eng-platform/references/3-cloud.md +24 -0
  163. package/skills/eng-platform/references/4-iac-automacao.md +24 -0
  164. package/skills/eng-platform/references/5-containers-orquestracao.md +25 -0
  165. package/skills/eng-platform/references/6-cicd-entrega.md +24 -0
  166. package/skills/eng-platform/references/7-observabilidade.md +24 -0
  167. package/skills/eng-platform/references/8-confiabilidade-sre.md +24 -0
  168. package/skills/eng-platform/references/9-seguranca-infra.md +26 -0
  169. package/skills/eng-platform/references/especializacoes.md +34 -0
  170. package/skills/eng-qa/SKILL.md +283 -0
  171. package/skills/eng-qa/references/1-fundamentos-qualidade.md +25 -0
  172. package/skills/eng-qa/references/10-confiabilidade.md +25 -0
  173. package/skills/eng-qa/references/11-seguranca.md +24 -0
  174. package/skills/eng-qa/references/12-acessibilidade.md +24 -0
  175. package/skills/eng-qa/references/13-dados-testes.md +24 -0
  176. package/skills/eng-qa/references/14-gestao-testes-defeitos.md +25 -0
  177. package/skills/eng-qa/references/15-arquitetura-qa.md +25 -0
  178. package/skills/eng-qa/references/2-estrategia-planejamento.md +25 -0
  179. package/skills/eng-qa/references/3-design-testes.md +25 -0
  180. package/skills/eng-qa/references/4-manual-exploratorio.md +24 -0
  181. package/skills/eng-qa/references/5-automacao-testes.md +25 -0
  182. package/skills/eng-qa/references/6-api-integracao.md +25 -0
  183. package/skills/eng-qa/references/7-contrato.md +24 -0
  184. package/skills/eng-qa/references/8-interface-compat-mobile.md +24 -0
  185. package/skills/eng-qa/references/9-performance.md +24 -0
  186. package/skills/eng-qa/references/especializacoes.md +29 -0
  187. package/skills/eng-qa-planner/SKILL.md +264 -0
  188. package/skills/jarvis-context-detect/SKILL.md +7 -1
  189. package/skills/jarvis-context-detect/assets/context-profile-template.md +1 -1
  190. package/skills/jarvis-create-specialization/SKILL.md +14 -0
  191. package/skills/jarvis-docs-central/README.md +31 -28
  192. package/skills/jarvis-docs-central/SKILL.md +11 -5
  193. package/skills/jarvis-docs-index/SKILL.md +10 -2
  194. package/skills/jarvis-evolution-architect/SKILL.md +6 -0
  195. package/skills/jarvis-init/SKILL.md +22 -15
  196. package/skills/jarvis-init/assets/passo-adicionar-stacks.md +47 -18
  197. package/skills/jarvis-list-specializations/SKILL.md +36 -17
  198. package/skills/jarvis-report-issue/SKILL.md +7 -1
  199. package/skills/product-roadmap-report/SKILL.md +8 -0
  200. package/skills/product-specs/SKILL.md +9 -0
  201. package/skills/product-specs-update/SKILL.md +6 -0
  202. package/taxonomy.md +16 -28
  203. package/templates/AGENTS.md +2 -0
  204. package/templates/ENV-template.md +10 -2
  205. package/templates/engineering/qa/qa.sprint-plan-template.md +1 -1
  206. package/templates/engineering/tech-spec-delivery-template.md +179 -0
  207. package/workflows/AGENTS.md +14 -2
  208. package/workflows/README.md +8 -16
  209. package/workflows/all-tools.md +7 -1
  210. package/workflows/engineering/data/data.contract.md +8 -0
  211. package/workflows/engineering/data/data.new-pipeline.md +15 -4
  212. package/workflows/engineering/eng.breakdown-subtasks.md +9 -0
  213. package/workflows/engineering/eng.bug-audit.md +10 -13
  214. package/workflows/engineering/eng.build-tech-spec.md +17 -146
  215. package/workflows/engineering/eng.create-ard-from-code.md +8 -0
  216. package/workflows/engineering/eng.create-ard.md +8 -0
  217. package/workflows/engineering/eng.create-rfc.md +7 -0
  218. package/workflows/engineering/eng.debug.md +5 -21
  219. package/workflows/engineering/eng.docs.md +9 -0
  220. package/workflows/engineering/eng.light-arch.md +7 -0
  221. package/workflows/engineering/eng.plan.md +15 -2
  222. package/workflows/engineering/eng.pr.md +24 -30
  223. package/workflows/engineering/eng.pre-pr.md +33 -15
  224. package/workflows/engineering/eng.review.md +18 -11
  225. package/workflows/engineering/eng.rpa.robot.md +17 -7
  226. package/workflows/engineering/eng.start.md +33 -12
  227. package/workflows/engineering/eng.work.md +25 -17
  228. package/workflows/engineering/frontend/eng.frontend-component.md +7 -0
  229. package/workflows/engineering/frontend/eng.frontend-perf-audit.md +8 -0
  230. package/workflows/engineering/frontend/eng.frontend-review.md +8 -0
  231. package/workflows/engineering/qa/eng.qa-refinement-entry.md +7 -0
  232. package/workflows/engineering/qa/eng.qa-release-signoff.md +6 -0
  233. package/workflows/engineering/qa/eng.qa-sprint-planning.md +7 -1
  234. package/workflows/product/prod.roadmap.preview.md +9 -0
  235. package/workflows/product/prod.spec.ard.md +97 -0
  236. package/workflows/product/prod.spec.breakdown.md +10 -0
  237. package/workflows/product/prod.spec.clarify.md +8 -2
  238. package/workflows/product/prod.spec.discovery.md +11 -1
  239. package/workflows/product/prod.spec.epic.md +8 -0
  240. package/workflows/product/prod.spec.frd.md +7 -0
  241. package/workflows/product/prod.spec.issue.md +6 -0
  242. package/workflows/product/prod.spec.md +15 -1
  243. package/workflows/product/prod.spec.prd.md +7 -0
  244. package/workflows/taxonomy.md +58 -6
  245. package/workflows/warm-up.md +47 -60
  246. package/agents/engineering/eng.cybersecurity.agent.md +0 -488
  247. package/agents/engineering/qa/eng.qa.cypress-specialist.md +0 -109
  248. package/bin/lib/docs/fetch-file.sh +0 -41
  249. package/bin/lib/docs/publish-file.sh +0 -284
  250. package/rules/engineering/eng.downstream-flow-rules.md +0 -297
  251. package/rules/engineering/qa/eng.qa.exploratory-session-rules.md +0 -137
  252. package/rules/engineering/qa/eng.qa.quality-gate-scoring-rules.md +0 -181
  253. package/rules/engineering/qa/eng.qa.tech-spec-validation-criteria-rules.md +0 -120
  254. package/skills/eng-ai-engineer/SKILL.md +0 -297
  255. package/skills/eng-data-bi/SKILL.md +0 -200
  256. package/skills/eng-data-debug/SKILL.md +0 -308
  257. package/skills/eng-data-engineer/SKILL.md +0 -257
  258. package/skills/eng-data-onboard/SKILL.md +0 -311
  259. package/skills/eng-data-orchestrator/SKILL.md +0 -427
  260. package/skills/eng-devops-performance-engineer/SKILL.md +0 -313
  261. package/skills/eng-frontend-design-system/SKILL.md +0 -621
  262. package/skills/eng-frontend-microfrontend/SKILL.md +0 -604
  263. package/skills/eng-global-browser-extension-builder/SKILL.md +0 -386
  264. package/skills/eng-global-jira-comment/SKILL.md +0 -21
  265. package/skills/eng-qa-a11y-audit/SKILL.md +0 -270
  266. package/skills/eng-qa-bug-report/SKILL.md +0 -1089
  267. package/skills/eng-qa-bug-report/TASK_MANAGERS.md +0 -138
  268. package/skills/eng-qa-cypress-e2e/SKILL.md +0 -178
  269. package/skills/eng-qa-dev-guide/SKILL.md +0 -165
  270. package/skills/eng-qa-e2e/SKILL.md +0 -401
  271. package/skills/eng-qa-e2e-spec-writer/SKILL.md +0 -323
  272. package/skills/eng-qa-exploratory/SKILL.md +0 -189
  273. package/skills/eng-qa-gate/SKILL.md +0 -371
  274. package/skills/eng-qa-gate/assets/checklist-validacao.md +0 -291
  275. package/skills/eng-qa-graphql-contract/SKILL.md +0 -257
  276. package/skills/eng-qa-quality-report/SKILL.md +0 -413
  277. package/skills/eng-qa-test-plan/SKILL.md +0 -467
  278. package/skills/eng-qa-testsprite/SKILL.md +0 -326
  279. package/skills/eng-qa-testsprite/references/testsprite-mcp.md +0 -224
  280. package/skills/eng-qa-unit-test/SKILL.md +0 -472
  281. package/skills/eng-scraper/SKILL.md +0 -684
  282. package/skills/eng-scraper-robot-builder/SKILL.md +0 -371
  283. package/skills/eng-security-cybersecurity/SKILL.md +0 -646
  284. package/skills/eng-security-patch/SKILL.md +0 -382
  285. package/skills/eng-security-threat-model/SKILL.md +0 -165
  286. package/skills/eng-security-triage/SKILL.md +0 -270
  287. package/templates/engineering/qa/eng.qa.quality-gate-examples-template.md +0 -311
  288. package/templates/engineering/qa/eng.qa.quality-gate-report-template.md +0 -249
  289. package/templates/engineering/qa/qa.cypress-test-template.md +0 -172
  290. package/templates/engineering/qa/qa.exploratory-session-template.md +0 -148
  291. package/templates/engineering/qa/qa.quality-report-template.md +0 -130
  292. package/workflows/engineering/eng.security-audit.md +0 -312
  293. package/workflows/engineering/eng.security-incident.md +0 -275
  294. package/workflows/engineering/eng.security-pipeline.md +0 -210
  295. package/workflows/engineering/eng.security-review.md +0 -235
  296. package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +0 -51
  297. package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +0 -51
  298. package/workflows/engineering/qa/eng.qa-exploratory-session.md +0 -60
  299. package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +0 -202
  300. package/workflows/engineering/qa/eng.qa-quality-report.md +0 -78
  301. /package/skills/{eng-qa-test-plan → eng-qa-planner}/assets/test-coverage-template.md +0 -0
  302. /package/skills/{eng-qa-test-plan → eng-qa-planner}/assets/test-patterns.md +0 -0
  303. /package/templates/{CDD aplicado a Prompts.md → cdd-aplicado-a-prompts.md} +0 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @fileoverview Estimativa do que o framework carrega no início de uma sessão.
3
+ *
4
+ * Mede, por tipo, os arquivos que a IDE lê no início para um perfil (HUB,
5
+ * POSITION, AREA, SQUAD). Tokens são uma **estimativa** (bytes ÷ 4): serve
6
+ * para comparar antes e depois, não para cobrar custo.
7
+ * @module core/token-report
8
+ */
9
+
10
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { listRulesForProfile } from "./profile-filter.js";
13
+
14
+ /** Bytes por token usados na estimativa. */
15
+ export const BYTES_PER_TOKEN = 4;
16
+
17
+ /**
18
+ * @param {number} bytes
19
+ * @returns {number} tokens estimados (bytes ÷ 4, arredondado)
20
+ */
21
+ export function estimateTokens(bytes) {
22
+ return Math.round(bytes / BYTES_PER_TOKEN);
23
+ }
24
+
25
+ /**
26
+ * Tamanho em bytes do bloco de frontmatter (do primeiro `---` ao segundo `---`).
27
+ * @param {string} content
28
+ * @returns {number} 0 quando não há frontmatter
29
+ */
30
+ export function frontmatterBytes(content) {
31
+ const match = content.match(/^---\s*\n[\s\S]*?\n---/);
32
+ return match ? Buffer.byteLength(match[0], "utf-8") : 0;
33
+ }
34
+
35
+ function fileBytes(path) {
36
+ return Buffer.byteLength(readFileSync(path, "utf-8"), "utf-8");
37
+ }
38
+
39
+ function category(id, label, files, note = "") {
40
+ const sorted = [...files].sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path));
41
+ const bytes = sorted.reduce((sum, f) => sum + f.bytes, 0);
42
+ return { id, label, note, files: sorted, bytes, tokens: estimateTokens(bytes) };
43
+ }
44
+
45
+ /**
46
+ * @param {string} root - Raiz do framework (`rules/`, `skills/`, `workflows/`, `AGENTS.md`)
47
+ * @param {import("./profile-filter.js").Profile} profile
48
+ * @param {{ rtkEnabled?: boolean }} [opts]
49
+ */
50
+ export function buildReport(root, profile, opts = {}) {
51
+ const rtkEnabled = Boolean(opts.rtkEnabled);
52
+
53
+ const rules = listRulesForProfile(join(root, "rules"), profile, { rtkEnabled });
54
+
55
+ const agentsMd = existsSync(join(root, "AGENTS.md"))
56
+ ? [{ path: "AGENTS.md", bytes: fileBytes(join(root, "AGENTS.md")) }]
57
+ : [];
58
+
59
+ const skillsDir = join(root, "skills");
60
+ const skills = [];
61
+ if (existsSync(skillsDir)) {
62
+ for (const name of readdirSync(skillsDir)) {
63
+ const skillMd = join(skillsDir, name, "SKILL.md");
64
+ if (!statSync(join(skillsDir, name)).isDirectory() || !existsSync(skillMd)) continue;
65
+ const bytes = frontmatterBytes(readFileSync(skillMd, "utf-8"));
66
+ if (bytes > 0) skills.push({ path: `skills/${name}/SKILL.md`, bytes });
67
+ }
68
+ }
69
+
70
+ const warmupPath = join(root, "workflows", "warm-up.md");
71
+ const warmup = existsSync(warmupPath)
72
+ ? [{ path: "workflows/warm-up.md", bytes: fileBytes(warmupPath) }]
73
+ : [];
74
+
75
+ const categories = [
76
+ category("rules", "Rules", rules, "carregadas no início pelo Claude Code"),
77
+ category("agents-md", "AGENTS.md", agentsMd, "instrução de projeto"),
78
+ category(
79
+ "skills-frontmatter",
80
+ "Frontmatter dos skills",
81
+ skills,
82
+ "sempre visível; o corpo só ao invocar"
83
+ ),
84
+ category("warmup", "warm-up", warmup, "só se executado no início da sessão"),
85
+ ];
86
+
87
+ const bytes = categories.reduce((sum, c) => sum + c.bytes, 0);
88
+ return {
89
+ profile: {
90
+ HUB: profile.HUB,
91
+ POSITION: profile.POSITION,
92
+ AREA: profile.AREA,
93
+ SQUAD: profile.SQUAD,
94
+ },
95
+ rtkEnabled,
96
+ estimate: `tokens = bytes ÷ ${BYTES_PER_TOKEN} (estimativa)`,
97
+ categories,
98
+ total: { bytes, tokens: estimateTokens(bytes) },
99
+ };
100
+ }
@@ -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) {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview Índice nome → artefato (workflows, skills e agentes ativos).
3
+ *
4
+ * Montado a cada execução, sem arquivo persistido. O nome identifica o item:
5
+ * - workflow: arquivo `workflows/**\/{nome}.md` (sem README e AGENTS)
6
+ * - skill: pasta `skills/{nome}/SKILL.md`
7
+ * - agente: arquivo `agents/**\/{nome}.md` (fora de `archive/`, sem README e AGENTS)
8
+ * @module flow-map/index
9
+ */
10
+
11
+ import { existsSync, readdirSync, statSync } from "node:fs";
12
+ import { basename, join, relative, sep } from "node:path";
13
+
14
+ const IGNORED_FILES = new Set(["README.md", "AGENTS.md"]);
15
+
16
+ function walkMarkdown(dir, skipDirs = []) {
17
+ if (!existsSync(dir)) return [];
18
+ return readdirSync(dir).flatMap((name) => {
19
+ const path = join(dir, name);
20
+ if (statSync(path).isDirectory()) {
21
+ return skipDirs.includes(name) ? [] : walkMarkdown(path, skipDirs);
22
+ }
23
+ return name.endsWith(".md") && !IGNORED_FILES.has(name) ? [path] : [];
24
+ });
25
+ }
26
+
27
+ /**
28
+ * @typedef {Object} FlowItem
29
+ * @property {string} name
30
+ * @property {"workflow" | "skill" | "agent"} type
31
+ * @property {string} path - Caminho relativo à raiz do framework, com `/`
32
+ */
33
+
34
+ /**
35
+ * @param {string} root - Raiz do framework (`workflows/`, `skills/`, `agents/`)
36
+ * @returns {{ items: Map<string, FlowItem>, duplicates: string[] }}
37
+ */
38
+ export function buildIndex(root) {
39
+ const items = new Map();
40
+ const duplicates = [];
41
+ const rel = (path) => relative(root, path).split(sep).join("/");
42
+
43
+ function add(item) {
44
+ if (items.has(item.name)) duplicates.push(item.name);
45
+ else items.set(item.name, item);
46
+ }
47
+
48
+ for (const file of walkMarkdown(join(root, "workflows"))) {
49
+ add({ name: basename(file, ".md"), type: "workflow", path: rel(file) });
50
+ }
51
+
52
+ const skillsDir = join(root, "skills");
53
+ if (existsSync(skillsDir)) {
54
+ for (const dir of readdirSync(skillsDir)) {
55
+ const skillMd = join(skillsDir, dir, "SKILL.md");
56
+ if (existsSync(skillMd)) add({ name: dir, type: "skill", path: rel(skillMd) });
57
+ }
58
+ }
59
+
60
+ for (const file of walkMarkdown(join(root, "agents"), ["archive"])) {
61
+ add({ name: basename(file, ".md"), type: "agent", path: rel(file) });
62
+ }
63
+
64
+ return { items, duplicates };
65
+ }