jarvis-ai-framework 2.0.0 → 2.1.1

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 (192) hide show
  1. package/AGENTS.md +12 -9
  2. package/agents/AGENTS.md +9 -1
  3. package/agents/README.md +2 -2
  4. package/agents/engineering/data/eng.data-engineer.agent.md +2 -2
  5. package/agents/engineering/eng.agent.md +4 -6
  6. package/agents/engineering/eng.bug-hunter.md +10 -5
  7. package/agents/engineering/eng.cybersecurity.agent.md +2 -4
  8. package/agents/engineering/eng.dev-code-reviewer.md +4 -0
  9. package/agents/engineering/eng.docs-writer.md +3 -3
  10. package/agents/engineering/eng.frontend.agent.md +17 -8
  11. package/agents/engineering/eng.rpa.agent.md +3 -5
  12. package/agents/engineering/eng.ux-designer.agent.md +4 -0
  13. package/agents/engineering/qa/eng.qa.cypress-specialist.md +8 -5
  14. package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +4 -0
  15. package/agents/engineering/qa/eng.qa.quality-strategist.md +18 -8
  16. package/agents/engineering/qa/eng.qa.test-architect.md +17 -6
  17. package/agents/engineering/qa/eng.qa.test-planner.md +17 -8
  18. package/agents/engineering/qa/eng.qa.testing-engineer.md +26 -9
  19. package/agents/product/prod.discovery-interviewer.md +22 -3
  20. package/agents/product/prod.pm-checker.md +4 -0
  21. package/bin/commands/map.js +43 -0
  22. package/bin/commands/tokens.js +93 -0
  23. package/bin/jarvis.js +10 -0
  24. package/bin/lib/config/constants.js +11 -0
  25. package/bin/lib/core/profile-filter.js +113 -0
  26. package/bin/lib/core/scanner.js +2 -2
  27. package/bin/lib/core/token-report.js +90 -0
  28. package/bin/lib/flow-map/index.js +65 -0
  29. package/bin/lib/flow-map/parse.js +217 -0
  30. package/bin/lib/flow-map/render.js +86 -0
  31. package/bin/lib/flow-map/tree.js +199 -0
  32. package/members.md +1 -1
  33. package/package.json +4 -2
  34. package/rules/AGENTS.md +18 -7
  35. package/rules/engineering/eng-rules.md +2 -12
  36. package/rules/engineering/eng-security-rules.md +1 -2
  37. package/rules/engineering/eng.integrations-rules.md +0 -4
  38. package/rules/engineering/eng.skills-rules.md +0 -4
  39. package/rules/engineering/eng.specializations-rules.md +0 -4
  40. package/rules/product/README.md +1 -0
  41. package/rules/product/prod-rules.md +1 -4
  42. package/rules/product/prod.spec-rules.md +0 -4
  43. package/rules/rtk-rules.md +0 -1
  44. package/{rules → rules-on-demand}/engineering/eng.breakdown-subtasks-rules.md +0 -4
  45. package/{rules → rules-on-demand}/engineering/eng.plan-rules.md +0 -4
  46. package/{rules → rules-on-demand}/engineering/eng.pr-rules.md +0 -4
  47. package/{rules → rules-on-demand}/engineering/eng.pre-pr-rules.md +0 -4
  48. package/{rules → rules-on-demand}/engineering/eng.start-rules.md +0 -4
  49. package/{rules → rules-on-demand}/engineering/eng.tech-spec-rules.md +1 -2
  50. package/{rules → rules-on-demand}/engineering/eng.work-rules.md +0 -4
  51. package/skills/AGENTS.md +22 -21
  52. package/skills/SKILLS-ROADMAP.md +0 -4
  53. package/skills/eng-ai-engineer/SKILL.md +6 -0
  54. package/skills/eng-backend/SKILL.md +6 -0
  55. package/skills/eng-backend-arch-c4/SKILL.md +10 -4
  56. package/skills/eng-backend-microservices-trace/SKILL.md +6 -0
  57. package/skills/eng-backend-nestjs/SKILL.md +16 -461
  58. package/skills/eng-backend-nestjs/references/1-arquitetura-modulos.md +54 -0
  59. package/skills/eng-backend-nestjs/references/2-guards-autenticacao.md +118 -0
  60. package/skills/eng-backend-nestjs/references/3-interceptors-pipes-filters.md +113 -0
  61. package/skills/eng-backend-nestjs/references/4-config-logging.md +61 -0
  62. package/skills/eng-backend-nestjs/references/5-testes.md +83 -0
  63. package/skills/eng-backend-nestjs/references/6-problemas-comuns.md +53 -0
  64. package/skills/eng-backend-rabbitmq/SKILL.md +15 -298
  65. package/skills/eng-backend-rabbitmq/references/1-referencia-endpoints.md +28 -0
  66. package/skills/eng-backend-rabbitmq/references/2-codigo-consumer-producer.md +90 -0
  67. package/skills/eng-backend-rabbitmq/references/3-arquiteturas-comuns.md +101 -0
  68. package/skills/eng-backend-rabbitmq/references/4-troubleshooting.md +93 -0
  69. package/skills/eng-data-bi/SKILL.md +6 -0
  70. package/skills/eng-data-debug/SKILL.md +6 -0
  71. package/skills/eng-data-engineer/SKILL.md +16 -7
  72. package/skills/eng-data-onboard/SKILL.md +6 -0
  73. package/skills/eng-data-orchestrator/SKILL.md +6 -0
  74. package/skills/eng-devops-performance-engineer/SKILL.md +6 -0
  75. package/skills/eng-frontend/SKILL.md +6 -0
  76. package/skills/eng-frontend-design-system/SKILL.md +13 -479
  77. package/skills/eng-frontend-design-system/references/1-estrutura-pacote.md +57 -0
  78. package/skills/eng-frontend-design-system/references/2-tokens.md +116 -0
  79. package/skills/eng-frontend-design-system/references/3-componentes.md +165 -0
  80. package/skills/eng-frontend-design-system/references/4-storybook.md +85 -0
  81. package/skills/eng-frontend-design-system/references/5-auditoria-versionamento.md +76 -0
  82. package/skills/eng-frontend-microfrontend/SKILL.md +17 -457
  83. package/skills/eng-frontend-microfrontend/references/1-criando-remote.md +109 -0
  84. package/skills/eng-frontend-microfrontend/references/2-configurando-shell.md +70 -0
  85. package/skills/eng-frontend-microfrontend/references/3-contratos-interface.md +73 -0
  86. package/skills/eng-frontend-microfrontend/references/4-shared-dependencies.md +48 -0
  87. package/skills/eng-frontend-microfrontend/references/5-event-bus.md +59 -0
  88. package/skills/eng-frontend-microfrontend/references/6-monorepo-deploy.md +77 -0
  89. package/skills/eng-frontend-microfrontend/references/7-troubleshooting.md +45 -0
  90. package/skills/eng-global-browser-extension-builder/SKILL.md +6 -0
  91. package/skills/eng-global-docs-write/SKILL.md +10 -5
  92. package/skills/eng-global-jira-comment/SKILL.md +6 -0
  93. package/skills/eng-global-pr/SKILL.md +7 -1
  94. package/skills/eng-global-task-comment/SKILL.md +6 -0
  95. package/skills/eng-qa-a11y-audit/SKILL.md +10 -4
  96. package/skills/eng-qa-bug-report/SKILL.md +18 -230
  97. package/skills/eng-qa-bug-report/references/1-integracao-task-managers.md +148 -0
  98. package/skills/eng-qa-bug-report/references/2-exemplos-de-uso.md +46 -0
  99. package/skills/eng-qa-bug-report/references/3-bug-to-test.md +45 -0
  100. package/skills/eng-qa-cypress-e2e/SKILL.md +6 -0
  101. package/skills/eng-qa-dev-guide/SKILL.md +9 -1
  102. package/skills/eng-qa-e2e/SKILL.md +9 -3
  103. package/skills/eng-qa-e2e-spec-writer/SKILL.md +15 -9
  104. package/skills/eng-qa-exploratory/SKILL.md +16 -9
  105. package/skills/eng-qa-gate/SKILL.md +11 -5
  106. package/skills/eng-qa-gate/assets/checklist-validacao.md +1 -1
  107. package/skills/eng-qa-graphql-contract/SKILL.md +9 -3
  108. package/skills/eng-qa-quality-report/SKILL.md +9 -3
  109. package/skills/eng-qa-test-plan/SKILL.md +6 -0
  110. package/skills/eng-qa-testsprite/SKILL.md +10 -4
  111. package/skills/eng-qa-unit-test/SKILL.md +7 -1
  112. package/skills/eng-scraper/SKILL.md +12 -436
  113. package/skills/eng-scraper/references/1-puppeteer.md +140 -0
  114. package/skills/eng-scraper/references/2-playwright-cheerio-pdf.md +82 -0
  115. package/skills/eng-scraper/references/3-anti-bot.md +94 -0
  116. package/skills/eng-scraper/references/4-resiliencia-etl-monitoramento.md +137 -0
  117. package/skills/eng-scraper-robot-builder/SKILL.md +6 -0
  118. package/skills/eng-security-cybersecurity/SKILL.md +13 -409
  119. package/skills/eng-security-cybersecurity/references/1-owasp-top-10.md +331 -0
  120. package/skills/eng-security-cybersecurity/references/2-secrets-management.md +31 -0
  121. package/skills/eng-security-cybersecurity/references/3-input-validation.md +43 -0
  122. package/skills/eng-security-cybersecurity/references/4-compliance.md +20 -0
  123. package/skills/eng-security-patch/SKILL.md +6 -0
  124. package/skills/eng-security-threat-model/SKILL.md +6 -0
  125. package/skills/eng-security-triage/SKILL.md +6 -0
  126. package/skills/jarvis-context-detect/SKILL.md +6 -0
  127. package/skills/jarvis-create-specialization/SKILL.md +6 -0
  128. package/skills/jarvis-docs-central/SKILL.md +6 -0
  129. package/skills/jarvis-docs-index/SKILL.md +10 -2
  130. package/skills/jarvis-evolution-architect/SKILL.md +439 -0
  131. package/skills/jarvis-evolution-architect/references/exemplos.md +297 -0
  132. package/skills/jarvis-evolution-architect/references/guia-de-uso.md +355 -0
  133. package/skills/jarvis-evolution-architect/references/lentes-e-conceitos.md +292 -0
  134. package/skills/jarvis-evolution-architect/references/visao-geral.md +272 -0
  135. package/skills/jarvis-init/SKILL.md +14 -7
  136. package/skills/jarvis-list-specializations/SKILL.md +6 -0
  137. package/skills/jarvis-report-issue/SKILL.md +6 -0
  138. package/skills/product-roadmap-report/SKILL.md +8 -0
  139. package/skills/product-specs/SKILL.md +9 -0
  140. package/skills/product-specs-update/SKILL.md +6 -0
  141. package/taxonomy.md +12 -12
  142. package/templates/AGENTS.md +2 -0
  143. package/templates/engineering/tech-spec-delivery-template.md +179 -0
  144. package/workflows/AGENTS.md +12 -2
  145. package/workflows/README.md +18 -16
  146. package/workflows/all-tools.md +7 -1
  147. package/workflows/engineering/data/data.contract.md +8 -0
  148. package/workflows/engineering/data/data.new-pipeline.md +10 -0
  149. package/workflows/engineering/eng.breakdown-subtasks.md +9 -0
  150. package/workflows/engineering/eng.bug-audit.md +9 -2
  151. package/workflows/engineering/eng.build-tech-spec.md +17 -146
  152. package/workflows/engineering/eng.create-ard-from-code.md +8 -0
  153. package/workflows/engineering/eng.create-ard.md +8 -0
  154. package/workflows/engineering/eng.create-rfc.md +7 -0
  155. package/workflows/engineering/eng.debug.md +3 -3
  156. package/workflows/engineering/eng.docs.md +9 -0
  157. package/workflows/engineering/eng.light-arch.md +7 -0
  158. package/workflows/engineering/eng.plan.md +15 -2
  159. package/workflows/engineering/eng.pr.md +19 -7
  160. package/workflows/engineering/eng.pre-pr.md +27 -1
  161. package/workflows/engineering/eng.review.md +13 -0
  162. package/workflows/engineering/eng.rpa.robot.md +16 -0
  163. package/workflows/engineering/eng.security-audit.md +7 -0
  164. package/workflows/engineering/eng.security-incident.md +6 -0
  165. package/workflows/engineering/eng.security-pipeline.md +9 -0
  166. package/workflows/engineering/eng.security-review.md +6 -0
  167. package/workflows/engineering/eng.start.md +22 -6
  168. package/workflows/engineering/eng.work.md +18 -2
  169. package/workflows/engineering/frontend/eng.frontend-component.md +7 -0
  170. package/workflows/engineering/frontend/eng.frontend-perf-audit.md +8 -0
  171. package/workflows/engineering/frontend/eng.frontend-review.md +8 -0
  172. package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +6 -0
  173. package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +6 -0
  174. package/workflows/engineering/qa/eng.qa-exploratory-session.md +8 -0
  175. package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +7 -0
  176. package/workflows/engineering/qa/eng.qa-quality-report.md +7 -0
  177. package/workflows/engineering/qa/eng.qa-refinement-entry.md +7 -0
  178. package/workflows/engineering/qa/eng.qa-release-signoff.md +6 -0
  179. package/workflows/engineering/qa/eng.qa-sprint-planning.md +6 -0
  180. package/workflows/product/prod.roadmap.preview.md +9 -0
  181. package/workflows/product/prod.spec.ard.md +97 -0
  182. package/workflows/product/prod.spec.breakdown.md +10 -0
  183. package/workflows/product/prod.spec.clarify.md +8 -2
  184. package/workflows/product/prod.spec.discovery.md +10 -0
  185. package/workflows/product/prod.spec.epic.md +8 -0
  186. package/workflows/product/prod.spec.frd.md +7 -0
  187. package/workflows/product/prod.spec.issue.md +6 -0
  188. package/workflows/product/prod.spec.md +15 -1
  189. package/workflows/product/prod.spec.prd.md +7 -0
  190. package/workflows/taxonomy.md +58 -6
  191. package/workflows/warm-up.md +24 -9
  192. package/rules/engineering/eng.downstream-flow-rules.md +0 -297
@@ -0,0 +1,93 @@
1
+ import { buildReport } from "../lib/core/token-report.js";
2
+ import { resolveEnvPath, loadEnv } from "../lib/env-loader.js";
3
+ import { getFrameworkRoot } from "../lib/utils/paths.js";
4
+ import {
5
+ showBanner,
6
+ RED,
7
+ GREEN,
8
+ BLUE,
9
+ YELLOW,
10
+ CYAN,
11
+ DIM,
12
+ NC,
13
+ } from "../lib/utils/ui.js";
14
+ import { logger } from "../lib/utils/logger.js";
15
+
16
+ const AXES = ["HUB", "POSITION", "AREA", "SQUAD"];
17
+
18
+ /** Perfil: as flags (`--hub` etc.) mandam; o que faltar vem do ENV.md. */
19
+ function resolveProfile(flags) {
20
+ const profile = {};
21
+ for (const axis of AXES) {
22
+ const value = flags[axis.toLowerCase()];
23
+ if (typeof value === "string") profile[axis] = value;
24
+ }
25
+
26
+ let rtkEnabled = flags.rtk === true || flags.rtk === "true";
27
+ // O ENV.md só entra quando falta algum eixo; com os 4 eixos nas flags, o RTK vem só da flag.
28
+ if (AXES.some((axis) => !profile[axis])) {
29
+ try {
30
+ const { path } = resolveEnvPath(process.cwd(), flags);
31
+ const env = loadEnv(process.cwd(), path);
32
+ for (const axis of AXES) {
33
+ if (!profile[axis] && env[axis]) profile[axis] = env[axis];
34
+ }
35
+ if (flags.rtk === undefined) rtkEnabled = env.RTK_ENABLED === "true";
36
+ } catch {
37
+ // sem ENV.md: valem só as flags
38
+ }
39
+ }
40
+
41
+ const missing = AXES.filter((axis) => !profile[axis]);
42
+ return { profile, rtkEnabled, missing };
43
+ }
44
+
45
+ const kb = (bytes) => (bytes / 1000).toFixed(1);
46
+
47
+ export async function tokens(flags) {
48
+ const { profile, rtkEnabled, missing } = resolveProfile(flags);
49
+
50
+ if (missing.length > 0) {
51
+ logger.error(`${RED}❌ Perfil incompleto: faltam ${missing.join(", ")}${NC}`);
52
+ logger.info(
53
+ `${YELLOW}Uso: jarvis tokens [--hub X --position Y --area Z --squad W] [--rtk] [--json]${NC}`,
54
+ );
55
+ logger.info(`${DIM}Sem flags, o perfil vem do ENV.md do workspace (use --ide <ide> se houver mais de um).${NC}\n`);
56
+ process.exit(1);
57
+ }
58
+
59
+ const report = buildReport(getFrameworkRoot(), profile, { rtkEnabled });
60
+
61
+ if (flags.json) {
62
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
63
+ return;
64
+ }
65
+
66
+ showBanner();
67
+ logger.info(`${CYAN}📏 Custo estimado no início da sessão${NC}`);
68
+ logger.info(
69
+ `${DIM}Perfil: HUB=${profile.HUB} | POSITION=${profile.POSITION} | AREA=${profile.AREA} | SQUAD=${profile.SQUAD}` +
70
+ ` | RTK=${rtkEnabled ? "ligado" : "desligado"}${NC}`,
71
+ );
72
+ logger.info(`${DIM}${report.estimate}${NC}`);
73
+ logger.info("");
74
+ logger.info(`${DIM}${"Tipo".padEnd(26)}${"Arquivos".padStart(9)}${"KB".padStart(10)}${"Tokens (≈)".padStart(13)}${NC}`);
75
+ logger.info(`${DIM}${"─".repeat(58)}${NC}`);
76
+
77
+ for (const c of report.categories) {
78
+ logger.info(
79
+ `${GREEN}${c.label.padEnd(26)}${NC}${String(c.files.length).padStart(9)}` +
80
+ `${kb(c.bytes).padStart(10)}${String(c.tokens).padStart(13)}`,
81
+ );
82
+ if (c.files[0]) {
83
+ logger.info(` ${DIM}maior: ${c.files[0].path} (${kb(c.files[0].bytes)} KB) — ${c.note}${NC}`);
84
+ }
85
+ }
86
+
87
+ logger.info(`${DIM}${"─".repeat(58)}${NC}`);
88
+ logger.info(
89
+ `${BLUE}${"Total".padEnd(26)}${NC}${"".padStart(9)}` +
90
+ `${kb(report.total.bytes).padStart(10)}${String(report.total.tokens).padStart(13)}`,
91
+ );
92
+ logger.info("");
93
+ }
package/bin/jarvis.js CHANGED
@@ -12,6 +12,8 @@ import { docsSync } from "./commands/docs-sync.js";
12
12
  import { docsPublish } from "./commands/docs-publish.js"
13
13
  import { qaSignoff } from "./commands/qa-signoff.js";
14
14
  import { installRtk } from "./commands/install-rtk.js";
15
+ import { tokens } from "./commands/tokens.js";
16
+ import { map } from "./commands/map.js";
15
17
  import { formatIDEsList } from "./lib/config/ide-config.js";
16
18
  import { showBanner, BLUE, GREEN, YELLOW, DIM, NC } from "./lib/utils/ui.js";
17
19
  import { logger } from "./lib/utils/logger.js";
@@ -63,6 +65,12 @@ function help() {
63
65
  logger.info(
64
66
  ` ${GREEN}jarvis install-rtk${NC} Instalar RTK (token killer — economia 60-90%)`,
65
67
  );
68
+ logger.info(
69
+ ` ${GREEN}jarvis tokens${NC} [--hub <h> --position <p> --area <a> --squad <s>] [--rtk] [--json] Estimar o que carrega no início da sessão (perfil do ENV.md se sem flags)`,
70
+ );
71
+ logger.info(
72
+ ` ${GREEN}jarvis map${NC} <nome> [--depth N] [--reverse] Mapa do que um workflow, skill ou agente chama (ou, com --reverse, quem o chama)`,
73
+ );
66
74
  logger.info(
67
75
  ` ${GREEN}jarvis qa-signoff${NC} [--branch <branch>] [--max-age <horas>] Verificar sign-off QA (usado pelo CI)`,
68
76
  );
@@ -142,6 +150,8 @@ const commands = {
142
150
  },
143
151
  "install-rtk": () => installRtk(flags),
144
152
  "qa-signoff": () => qaSignoff(flags),
153
+ tokens: () => tokens(flags),
154
+ map: () => map(flags),
145
155
  "--version": () => logger.info(getVersion()),
146
156
  "-v": () => logger.info(getVersion()),
147
157
  "--help": () => help(),
@@ -19,6 +19,7 @@ export const SYNC_DIRS = Object.freeze([
19
19
  "workflows",
20
20
  "templates",
21
21
  "rules",
22
+ "rules-on-demand",
22
23
  "scripts",
23
24
  ]);
24
25
 
@@ -67,6 +68,16 @@ export const OBSOLETE_PATHS = Object.freeze([
67
68
  "skills/report-issue",
68
69
  "skills/docs-central",
69
70
  "skills/docs-index",
71
+ // eng.downstream-flow-rules (removida): processo de board de um time, acoplado a Jira
72
+ "rules/engineering/eng.downstream-flow-rules.md",
73
+ // rules de uma etapa só: saíram de rules/ (carregada no início) para rules-on-demand/ (lida pelo workflow)
74
+ "rules/engineering/eng.start-rules.md",
75
+ "rules/engineering/eng.plan-rules.md",
76
+ "rules/engineering/eng.work-rules.md",
77
+ "rules/engineering/eng.pr-rules.md",
78
+ "rules/engineering/eng.pre-pr-rules.md",
79
+ "rules/engineering/eng.tech-spec-rules.md",
80
+ "rules/engineering/eng.breakdown-subtasks-rules.md",
70
81
  ]);
71
82
 
72
83
  /**
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @fileoverview Filtro de rules por perfil (bloco "Applies to").
3
+ *
4
+ * Espelha em JS a lógica do Passo 8 ("Profile-Aware Rules Sync") do skill
5
+ * `skills/jarvis-init/SKILL.md`, que é executada pela IA. As duas fontes da
6
+ * regra são esse passo e a seção "Profile-Aware Rules Loading" de
7
+ * `rules/AGENTS.md`: ao mudar uma, mude as três.
8
+ *
9
+ * Regras:
10
+ * - Os quatro eixos (HUB, POSITION, AREA, SQUAD) são combinados com AND.
11
+ * - `all` casa qualquer valor; listas são separadas por vírgula.
12
+ * - FULLCYCLE (HUB) satisfaz qualquer HUB.
13
+ * - GENERALIST (POSITION) satisfaz qualquer POSITION e qualquer AREA.
14
+ * - Arquivo sem bloco "Applies to" é universal.
15
+ * - `rules/AGENTS.md` nunca é filtrado.
16
+ * - `rtk-rules.md` só entra com RTK habilitado (opt-in).
17
+ * @module core/profile-filter
18
+ */
19
+
20
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
21
+ import { join, relative, sep } from "node:path";
22
+
23
+ const APPLIES_TO_RE =
24
+ /> \*\*Applies to:\*\* HUB: ([^|]+)\| POSITION: ([^|]+)\| AREA: ([^|]+)\| SQUAD: (.+)/;
25
+
26
+ /**
27
+ * @typedef {Object} AppliesTo
28
+ * @property {string} HUB
29
+ * @property {string} POSITION
30
+ * @property {string} AREA
31
+ * @property {string} SQUAD
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} Profile
36
+ * @property {string} HUB
37
+ * @property {string} POSITION
38
+ * @property {string} AREA
39
+ * @property {string} SQUAD
40
+ */
41
+
42
+ /**
43
+ * Lê o primeiro bloco "Applies to" do conteúdo.
44
+ * @param {string} content
45
+ * @returns {AppliesTo | null} `null` quando não há bloco (arquivo universal)
46
+ */
47
+ export function parseAppliesTo(content) {
48
+ const match = content.match(APPLIES_TO_RE);
49
+ if (!match) return null;
50
+ return {
51
+ HUB: match[1].trim(),
52
+ POSITION: match[2].trim(),
53
+ AREA: match[3].trim(),
54
+ SQUAD: match[4].trim(),
55
+ };
56
+ }
57
+
58
+ function axisMatches(list, value) {
59
+ if (list.toLowerCase() === "all") return true;
60
+ return list.split(",").map((s) => s.trim()).includes(value);
61
+ }
62
+
63
+ /**
64
+ * Decide se um bloco "Applies to" casa com o perfil.
65
+ * @param {AppliesTo | null} appliesTo
66
+ * @param {Profile} profile
67
+ * @returns {boolean}
68
+ */
69
+ export function matchesProfile(appliesTo, profile) {
70
+ if (!appliesTo) return true;
71
+ const hub = profile.HUB === "FULLCYCLE" || axisMatches(appliesTo.HUB, profile.HUB);
72
+ const generalist = profile.POSITION === "GENERALIST";
73
+ const position = generalist || axisMatches(appliesTo.POSITION, profile.POSITION);
74
+ const area = generalist || axisMatches(appliesTo.AREA, profile.AREA);
75
+ const squad = axisMatches(appliesTo.SQUAD, profile.SQUAD);
76
+ return hub && position && area && squad;
77
+ }
78
+
79
+ function walkMarkdown(dir) {
80
+ return readdirSync(dir).flatMap((name) => {
81
+ const p = join(dir, name);
82
+ if (statSync(p).isDirectory()) return walkMarkdown(p);
83
+ return p.endsWith(".md") ? [p] : [];
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Lista as rules que o perfil recebe, com o tamanho em bytes.
89
+ * @param {string} rulesDir - Pasta `rules/` do framework
90
+ * @param {Profile} profile
91
+ * @param {{ rtkEnabled?: boolean }} [opts]
92
+ * @returns {Array<{ path: string, bytes: number }>} Ordenado do maior para o menor;
93
+ * `path` é relativo a `rulesDir`, com `/` como separador
94
+ */
95
+ export function listRulesForProfile(rulesDir, profile, opts = {}) {
96
+ if (!existsSync(rulesDir)) return [];
97
+ const result = [];
98
+ for (const file of walkMarkdown(rulesDir)) {
99
+ const rel = relative(rulesDir, file).split(sep).join("/");
100
+ const content = readFileSync(file, "utf-8");
101
+ const bytes = Buffer.byteLength(content, "utf-8");
102
+
103
+ if (rel === "AGENTS.md") {
104
+ result.push({ path: rel, bytes });
105
+ continue;
106
+ }
107
+ if (rel.endsWith("rtk-rules.md") && !opts.rtkEnabled) continue;
108
+ if (matchesProfile(parseAppliesTo(content), profile)) {
109
+ result.push({ path: rel, bytes });
110
+ }
111
+ }
112
+ return result.sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path));
113
+ }
@@ -26,10 +26,10 @@ function parseFrontmatter(content) {
26
26
  function extractDeps(content) {
27
27
  const deps = { rules: [], templates: [], skills: [], agents: [] };
28
28
 
29
- const ruleFile = content.match(/rules_file:\s*"?\$IDE\/rules\/(.+?)\.md"?/);
29
+ const ruleFile = content.match(/rules_file:\s*"?\$IDE\/rules(?:-on-demand)?\/(.+?)\.md"?/);
30
30
  if (ruleFile) deps.rules.push(ruleFile[1]);
31
31
 
32
- for (const m of content.matchAll(/rules\/([\w./-]+)\.md/g)) {
32
+ for (const m of content.matchAll(/rules(?:-on-demand)?\/([\w./-]+)\.md/g)) {
33
33
  if (!deps.rules.includes(m[1])) deps.rules.push(m[1]);
34
34
  }
35
35
 
@@ -0,0 +1,90 @@
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("skills-frontmatter", "Frontmatter dos skills", skills, "sempre visível; o corpo só ao invocar"),
79
+ category("warmup", "warm-up", warmup, "só se executado no início da sessão"),
80
+ ];
81
+
82
+ const bytes = categories.reduce((sum, c) => sum + c.bytes, 0);
83
+ return {
84
+ profile: { HUB: profile.HUB, POSITION: profile.POSITION, AREA: profile.AREA, SQUAD: profile.SQUAD },
85
+ rtkEnabled,
86
+ estimate: `tokens = bytes ÷ ${BYTES_PER_TOKEN} (estimativa)`,
87
+ categories,
88
+ total: { bytes, tokens: estimateTokens(bytes) },
89
+ };
90
+ }
@@ -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
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @fileoverview Leitor das declarações padronizadas de chamadas.
3
+ *
4
+ * Só lê a estrutura (sem interpretar prosa, sem IA):
5
+ * - workflows e skills: tabela `| Passo | Skill | Condição |` sob
6
+ * `### Skills invocados durante o workflow` (ou `... durante a execução do skill`);
7
+ * - agentes: `## Skills Disponíveis` (`### {skill}`) e `## Workflows e Agentes Relacionados`.
8
+ * @module flow-map/parse
9
+ */
10
+
11
+ export const WORKFLOW_TITLE = "Skills invocados durante o workflow";
12
+ export const SKILL_TITLE = "Skills invocados durante a execução do skill";
13
+
14
+ /**
15
+ * @typedef {Object} Target
16
+ * @property {"internal" | "agent" | "cli" | "mcp" | "external" | "unresolved"} kind
17
+ * @property {string} name
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} Row
22
+ * @property {string | null} passo
23
+ * @property {string} condicao
24
+ * @property {Target[]} targets
25
+ * @property {string | null} via - Texto de `(via ...)`, quando houver
26
+ * @property {"quebrada" | "a confirmar" | null} marker
27
+ * @property {boolean} selfContained - Linha "Nenhuma — … autocontido"
28
+ * @property {boolean} available - Skill que o agente pode usar (não é chamada fixa)
29
+ */
30
+
31
+ /** Troca o conteúdo dos blocos de código por linhas em branco (títulos dentro deles não valem). */
32
+ function stripFences(text) {
33
+ let fence = false;
34
+ return text
35
+ .split("\n")
36
+ .map((line) => {
37
+ if (line.trimStart().startsWith("```")) {
38
+ fence = !fence;
39
+ return "";
40
+ }
41
+ return fence ? "" : line;
42
+ })
43
+ .join("\n");
44
+ }
45
+
46
+ /** Divide uma linha de tabela em células, ignorando `|` dentro de crases. */
47
+ export function splitCells(line) {
48
+ const cells = [];
49
+ let current = "";
50
+ let inCode = false;
51
+ for (const ch of line.trim()) {
52
+ if (ch === "`") inCode = !inCode;
53
+ if (ch === "|" && !inCode) {
54
+ cells.push(current.trim());
55
+ current = "";
56
+ } else {
57
+ current += ch;
58
+ }
59
+ }
60
+ cells.push(current.trim());
61
+ if (cells[0] === "") cells.shift();
62
+ if (cells[cells.length - 1] === "") cells.pop();
63
+ return cells;
64
+ }
65
+
66
+ /**
67
+ * Classifica a célula `Skill` de uma linha da tabela.
68
+ * @param {string} cell
69
+ * @returns {{ targets: Target[], via: string | null, marker: Row["marker"], selfContained: boolean }}
70
+ */
71
+ export function classifyCell(cell) {
72
+ const text = cell.trim();
73
+ if (/^nenhuma/i.test(text)) return { targets: [], via: null, marker: null, selfContained: true };
74
+
75
+ const marker = /refer[eê]ncia quebrada/i.test(text)
76
+ ? "quebrada"
77
+ : /refer[eê]ncia a confirmar/i.test(text)
78
+ ? "a confirmar"
79
+ : null;
80
+
81
+ let via = null;
82
+ let analyzed = text;
83
+ const viaMatch = text.match(/\(via\s+([^)]*)\)/);
84
+ if (viaMatch) {
85
+ via = viaMatch[1].replace(/`/g, "").trim();
86
+ analyzed = text.replace(viaMatch[0], "");
87
+ }
88
+
89
+ const spans = [...analyzed.matchAll(/`([^`]+)`(\s*\(agente\))?/g)];
90
+ const targets = [];
91
+ const external = /skill externo/i.test(analyzed); // skill de fora do framework: não está no índice
92
+
93
+ if (/\(MCP/.test(analyzed) && spans[0]) {
94
+ targets.push({ kind: "mcp", name: spans[0][1].trim() });
95
+ return { targets, via, marker, selfContained: false };
96
+ }
97
+
98
+ for (const span of spans) {
99
+ const token = span[1].trim();
100
+ if (span[2]) targets.push({ kind: "agent", name: token });
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 });
105
+ else if (token.startsWith("mcp__")) targets.push({ kind: "mcp", name: token });
106
+ // Read, Grep, Glob e texto livre não são chamadas a artefatos: ignorados
107
+ }
108
+
109
+ if (marker && targets.length === 0 && spans[0]) {
110
+ targets.push({ kind: "unresolved", name: spans[0][1].trim() });
111
+ }
112
+ return { targets, via, marker, selfContained: false };
113
+ }
114
+
115
+ function parseTable(text, title) {
116
+ const lines = stripFences(text).split("\n");
117
+ const start = lines.findIndex((l) => l.trim() === `### ${title}`);
118
+ if (start === -1) return null;
119
+ const rows = [];
120
+ let i = start + 1;
121
+ while (i < lines.length && !lines[i].startsWith("|")) {
122
+ if (/^#{1,3} /.test(lines[i])) return rows;
123
+ i++;
124
+ }
125
+ i += 2; // cabeçalho e separador
126
+ for (; i < lines.length && lines[i].startsWith("|"); i++) {
127
+ const [passo = "", skill = "", condicao = ""] = splitCells(lines[i]);
128
+ const c = classifyCell(skill);
129
+ rows.push({
130
+ passo,
131
+ condicao,
132
+ targets: c.targets,
133
+ via: c.via,
134
+ marker: c.marker,
135
+ selfContained: c.selfContained,
136
+ available: false,
137
+ });
138
+ }
139
+ return rows;
140
+ }
141
+
142
+ function section(text, title) {
143
+ const m = text.match(new RegExp(`^## ${title}\\s*\\n([\\s\\S]*?)(?=\\n## |\\n---\\s*\\n|(?![\\s\\S]))`, "m"));
144
+ return m ? m[1] : null;
145
+ }
146
+
147
+ function parseAgent(text) {
148
+ const clean = stripFences(text);
149
+ const rows = [];
150
+
151
+ const skills = section(clean, "Skills Disponíveis");
152
+ if (skills !== null) {
153
+ for (const block of skills.split(/^### /m).slice(1)) {
154
+ const [heading, ...rest] = block.split("\n");
155
+ // 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]))];
157
+ if (names.length === 0) {
158
+ const token = heading.trim().split(/[\s(]/)[0];
159
+ if (/[-.]/.test(token)) names.push(token);
160
+ }
161
+ const context = rest.map((l) => l.trim()).find((l) => l && !l.startsWith("- ")) ?? "";
162
+ for (const name of names) {
163
+ rows.push({
164
+ passo: null,
165
+ condicao: context.replace(/:$/, ""),
166
+ targets: [{ kind: "internal", name }],
167
+ via: null,
168
+ marker: null,
169
+ selfContained: false,
170
+ available: true,
171
+ });
172
+ }
173
+ }
174
+ }
175
+
176
+ const related = section(clean, "Workflows e Agentes Relacionados");
177
+ if (related !== null) {
178
+ for (const line of related.split("\n")) {
179
+ if (!/^- /.test(line) || /^- Arquivo:/.test(line)) continue;
180
+ const bold = line.match(/\*\*([^*]+)\*\*/);
181
+ const code = line.match(/`([^`]+)`/);
182
+ let name = bold ? bold[1] : code ? code[1] : null;
183
+ if (!name) continue;
184
+ if (name.includes("/")) {
185
+ if (!name.endsWith(".md")) continue;
186
+ name = name.split("/").pop().replace(/\.md$/, "");
187
+ }
188
+ const condicao = line.includes(":") ? line.slice(line.indexOf(":") + 1).trim() : "";
189
+ rows.push({
190
+ passo: null,
191
+ condicao,
192
+ targets: [{ kind: "internal", name }],
193
+ via: null,
194
+ marker: null,
195
+ selfContained: false,
196
+ available: false,
197
+ });
198
+ }
199
+ }
200
+
201
+ const declared = skills !== null || related !== null;
202
+ return { declared, selfContained: declared && rows.length === 0, rows };
203
+ }
204
+
205
+ /**
206
+ * Lê as chamadas declaradas por um artefato.
207
+ * @param {"workflow" | "skill" | "agent"} type
208
+ * @param {string} text
209
+ * @returns {{ declared: boolean, selfContained: boolean, rows: Row[] }}
210
+ */
211
+ export function parseDeclaration(type, text) {
212
+ if (type === "agent") return parseAgent(text);
213
+ const rows = parseTable(text, type === "workflow" ? WORKFLOW_TITLE : SKILL_TITLE);
214
+ if (rows === null) return { declared: false, selfContained: false, rows: [] };
215
+ const hasTargets = rows.some((r) => r.targets.length > 0);
216
+ return { declared: true, selfContained: rows.some((r) => r.selfContained) && !hasTargets, rows };
217
+ }