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