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.
- package/AGENTS.md +12 -9
- package/agents/AGENTS.md +9 -1
- package/agents/README.md +2 -2
- package/agents/engineering/data/eng.data-engineer.agent.md +2 -2
- package/agents/engineering/eng.agent.md +4 -6
- package/agents/engineering/eng.bug-hunter.md +10 -5
- package/agents/engineering/eng.cybersecurity.agent.md +2 -4
- package/agents/engineering/eng.dev-code-reviewer.md +4 -0
- package/agents/engineering/eng.docs-writer.md +3 -3
- package/agents/engineering/eng.frontend.agent.md +17 -8
- package/agents/engineering/eng.rpa.agent.md +3 -5
- package/agents/engineering/eng.ux-designer.agent.md +4 -0
- package/agents/engineering/qa/eng.qa.cypress-specialist.md +8 -5
- package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +4 -0
- package/agents/engineering/qa/eng.qa.quality-strategist.md +18 -8
- package/agents/engineering/qa/eng.qa.test-architect.md +17 -6
- package/agents/engineering/qa/eng.qa.test-planner.md +17 -8
- package/agents/engineering/qa/eng.qa.testing-engineer.md +26 -9
- package/agents/product/prod.discovery-interviewer.md +22 -3
- package/agents/product/prod.pm-checker.md +4 -0
- package/bin/commands/map.js +43 -0
- package/bin/commands/tokens.js +93 -0
- package/bin/jarvis.js +10 -0
- package/bin/lib/config/constants.js +11 -0
- package/bin/lib/core/profile-filter.js +113 -0
- package/bin/lib/core/scanner.js +2 -2
- package/bin/lib/core/token-report.js +90 -0
- package/bin/lib/flow-map/index.js +65 -0
- package/bin/lib/flow-map/parse.js +217 -0
- package/bin/lib/flow-map/render.js +86 -0
- package/bin/lib/flow-map/tree.js +199 -0
- package/members.md +1 -1
- package/package.json +4 -2
- package/rules/AGENTS.md +18 -7
- package/rules/engineering/eng-rules.md +2 -12
- package/rules/engineering/eng-security-rules.md +1 -2
- package/rules/engineering/eng.integrations-rules.md +0 -4
- package/rules/engineering/eng.skills-rules.md +0 -4
- package/rules/engineering/eng.specializations-rules.md +0 -4
- package/rules/product/README.md +1 -0
- package/rules/product/prod-rules.md +1 -4
- package/rules/product/prod.spec-rules.md +0 -4
- package/rules/rtk-rules.md +0 -1
- package/{rules → rules-on-demand}/engineering/eng.breakdown-subtasks-rules.md +0 -4
- package/{rules → rules-on-demand}/engineering/eng.plan-rules.md +0 -4
- package/{rules → rules-on-demand}/engineering/eng.pr-rules.md +0 -4
- package/{rules → rules-on-demand}/engineering/eng.pre-pr-rules.md +0 -4
- package/{rules → rules-on-demand}/engineering/eng.start-rules.md +0 -4
- package/{rules → rules-on-demand}/engineering/eng.tech-spec-rules.md +1 -2
- package/{rules → rules-on-demand}/engineering/eng.work-rules.md +0 -4
- package/skills/AGENTS.md +22 -21
- package/skills/SKILLS-ROADMAP.md +0 -4
- package/skills/eng-ai-engineer/SKILL.md +6 -0
- package/skills/eng-backend/SKILL.md +6 -0
- package/skills/eng-backend-arch-c4/SKILL.md +10 -4
- package/skills/eng-backend-microservices-trace/SKILL.md +6 -0
- package/skills/eng-backend-nestjs/SKILL.md +16 -461
- package/skills/eng-backend-nestjs/references/1-arquitetura-modulos.md +54 -0
- package/skills/eng-backend-nestjs/references/2-guards-autenticacao.md +118 -0
- package/skills/eng-backend-nestjs/references/3-interceptors-pipes-filters.md +113 -0
- package/skills/eng-backend-nestjs/references/4-config-logging.md +61 -0
- package/skills/eng-backend-nestjs/references/5-testes.md +83 -0
- package/skills/eng-backend-nestjs/references/6-problemas-comuns.md +53 -0
- package/skills/eng-backend-rabbitmq/SKILL.md +15 -298
- package/skills/eng-backend-rabbitmq/references/1-referencia-endpoints.md +28 -0
- package/skills/eng-backend-rabbitmq/references/2-codigo-consumer-producer.md +90 -0
- package/skills/eng-backend-rabbitmq/references/3-arquiteturas-comuns.md +101 -0
- package/skills/eng-backend-rabbitmq/references/4-troubleshooting.md +93 -0
- package/skills/eng-data-bi/SKILL.md +6 -0
- package/skills/eng-data-debug/SKILL.md +6 -0
- package/skills/eng-data-engineer/SKILL.md +16 -7
- package/skills/eng-data-onboard/SKILL.md +6 -0
- package/skills/eng-data-orchestrator/SKILL.md +6 -0
- package/skills/eng-devops-performance-engineer/SKILL.md +6 -0
- package/skills/eng-frontend/SKILL.md +6 -0
- package/skills/eng-frontend-design-system/SKILL.md +13 -479
- package/skills/eng-frontend-design-system/references/1-estrutura-pacote.md +57 -0
- package/skills/eng-frontend-design-system/references/2-tokens.md +116 -0
- package/skills/eng-frontend-design-system/references/3-componentes.md +165 -0
- package/skills/eng-frontend-design-system/references/4-storybook.md +85 -0
- package/skills/eng-frontend-design-system/references/5-auditoria-versionamento.md +76 -0
- package/skills/eng-frontend-microfrontend/SKILL.md +17 -457
- package/skills/eng-frontend-microfrontend/references/1-criando-remote.md +109 -0
- package/skills/eng-frontend-microfrontend/references/2-configurando-shell.md +70 -0
- package/skills/eng-frontend-microfrontend/references/3-contratos-interface.md +73 -0
- package/skills/eng-frontend-microfrontend/references/4-shared-dependencies.md +48 -0
- package/skills/eng-frontend-microfrontend/references/5-event-bus.md +59 -0
- package/skills/eng-frontend-microfrontend/references/6-monorepo-deploy.md +77 -0
- package/skills/eng-frontend-microfrontend/references/7-troubleshooting.md +45 -0
- package/skills/eng-global-browser-extension-builder/SKILL.md +6 -0
- package/skills/eng-global-docs-write/SKILL.md +10 -5
- package/skills/eng-global-jira-comment/SKILL.md +6 -0
- package/skills/eng-global-pr/SKILL.md +7 -1
- package/skills/eng-global-task-comment/SKILL.md +6 -0
- package/skills/eng-qa-a11y-audit/SKILL.md +10 -4
- package/skills/eng-qa-bug-report/SKILL.md +18 -230
- package/skills/eng-qa-bug-report/references/1-integracao-task-managers.md +148 -0
- package/skills/eng-qa-bug-report/references/2-exemplos-de-uso.md +46 -0
- package/skills/eng-qa-bug-report/references/3-bug-to-test.md +45 -0
- package/skills/eng-qa-cypress-e2e/SKILL.md +6 -0
- package/skills/eng-qa-dev-guide/SKILL.md +9 -1
- package/skills/eng-qa-e2e/SKILL.md +9 -3
- package/skills/eng-qa-e2e-spec-writer/SKILL.md +15 -9
- package/skills/eng-qa-exploratory/SKILL.md +16 -9
- package/skills/eng-qa-gate/SKILL.md +11 -5
- package/skills/eng-qa-gate/assets/checklist-validacao.md +1 -1
- package/skills/eng-qa-graphql-contract/SKILL.md +9 -3
- package/skills/eng-qa-quality-report/SKILL.md +9 -3
- package/skills/eng-qa-test-plan/SKILL.md +6 -0
- package/skills/eng-qa-testsprite/SKILL.md +10 -4
- package/skills/eng-qa-unit-test/SKILL.md +7 -1
- package/skills/eng-scraper/SKILL.md +12 -436
- package/skills/eng-scraper/references/1-puppeteer.md +140 -0
- package/skills/eng-scraper/references/2-playwright-cheerio-pdf.md +82 -0
- package/skills/eng-scraper/references/3-anti-bot.md +94 -0
- package/skills/eng-scraper/references/4-resiliencia-etl-monitoramento.md +137 -0
- package/skills/eng-scraper-robot-builder/SKILL.md +6 -0
- package/skills/eng-security-cybersecurity/SKILL.md +13 -409
- package/skills/eng-security-cybersecurity/references/1-owasp-top-10.md +331 -0
- package/skills/eng-security-cybersecurity/references/2-secrets-management.md +31 -0
- package/skills/eng-security-cybersecurity/references/3-input-validation.md +43 -0
- package/skills/eng-security-cybersecurity/references/4-compliance.md +20 -0
- package/skills/eng-security-patch/SKILL.md +6 -0
- package/skills/eng-security-threat-model/SKILL.md +6 -0
- package/skills/eng-security-triage/SKILL.md +6 -0
- package/skills/jarvis-context-detect/SKILL.md +6 -0
- package/skills/jarvis-create-specialization/SKILL.md +6 -0
- package/skills/jarvis-docs-central/SKILL.md +6 -0
- package/skills/jarvis-docs-index/SKILL.md +10 -2
- package/skills/jarvis-evolution-architect/SKILL.md +439 -0
- package/skills/jarvis-evolution-architect/references/exemplos.md +297 -0
- package/skills/jarvis-evolution-architect/references/guia-de-uso.md +355 -0
- package/skills/jarvis-evolution-architect/references/lentes-e-conceitos.md +292 -0
- package/skills/jarvis-evolution-architect/references/visao-geral.md +272 -0
- package/skills/jarvis-init/SKILL.md +14 -7
- package/skills/jarvis-list-specializations/SKILL.md +6 -0
- package/skills/jarvis-report-issue/SKILL.md +6 -0
- package/skills/product-roadmap-report/SKILL.md +8 -0
- package/skills/product-specs/SKILL.md +9 -0
- package/skills/product-specs-update/SKILL.md +6 -0
- package/taxonomy.md +12 -12
- package/templates/AGENTS.md +2 -0
- package/templates/engineering/tech-spec-delivery-template.md +179 -0
- package/workflows/AGENTS.md +12 -2
- package/workflows/README.md +18 -16
- package/workflows/all-tools.md +7 -1
- package/workflows/engineering/data/data.contract.md +8 -0
- package/workflows/engineering/data/data.new-pipeline.md +10 -0
- package/workflows/engineering/eng.breakdown-subtasks.md +9 -0
- package/workflows/engineering/eng.bug-audit.md +9 -2
- package/workflows/engineering/eng.build-tech-spec.md +17 -146
- package/workflows/engineering/eng.create-ard-from-code.md +8 -0
- package/workflows/engineering/eng.create-ard.md +8 -0
- package/workflows/engineering/eng.create-rfc.md +7 -0
- package/workflows/engineering/eng.debug.md +3 -3
- package/workflows/engineering/eng.docs.md +9 -0
- package/workflows/engineering/eng.light-arch.md +7 -0
- package/workflows/engineering/eng.plan.md +15 -2
- package/workflows/engineering/eng.pr.md +19 -7
- package/workflows/engineering/eng.pre-pr.md +27 -1
- package/workflows/engineering/eng.review.md +13 -0
- package/workflows/engineering/eng.rpa.robot.md +16 -0
- package/workflows/engineering/eng.security-audit.md +7 -0
- package/workflows/engineering/eng.security-incident.md +6 -0
- package/workflows/engineering/eng.security-pipeline.md +9 -0
- package/workflows/engineering/eng.security-review.md +6 -0
- package/workflows/engineering/eng.start.md +22 -6
- package/workflows/engineering/eng.work.md +18 -2
- package/workflows/engineering/frontend/eng.frontend-component.md +7 -0
- package/workflows/engineering/frontend/eng.frontend-perf-audit.md +8 -0
- package/workflows/engineering/frontend/eng.frontend-review.md +8 -0
- package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +6 -0
- package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +6 -0
- package/workflows/engineering/qa/eng.qa-exploratory-session.md +8 -0
- package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +7 -0
- package/workflows/engineering/qa/eng.qa-quality-report.md +7 -0
- package/workflows/engineering/qa/eng.qa-refinement-entry.md +7 -0
- package/workflows/engineering/qa/eng.qa-release-signoff.md +6 -0
- package/workflows/engineering/qa/eng.qa-sprint-planning.md +6 -0
- package/workflows/product/prod.roadmap.preview.md +9 -0
- package/workflows/product/prod.spec.ard.md +97 -0
- package/workflows/product/prod.spec.breakdown.md +10 -0
- package/workflows/product/prod.spec.clarify.md +8 -2
- package/workflows/product/prod.spec.discovery.md +10 -0
- package/workflows/product/prod.spec.epic.md +8 -0
- package/workflows/product/prod.spec.frd.md +7 -0
- package/workflows/product/prod.spec.issue.md +6 -0
- package/workflows/product/prod.spec.md +15 -1
- package/workflows/product/prod.spec.prd.md +7 -0
- package/workflows/taxonomy.md +58 -6
- package/workflows/warm-up.md +24 -9
- 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
|
+
}
|
package/bin/lib/core/scanner.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
}
|