jarvis-ai-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +416 -0
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/agents/AGENTS.md +234 -0
- package/agents/README.md +309 -0
- package/agents/engineering/data/eng.data-engineer.agent.md +309 -0
- package/agents/engineering/eng.agent.md +303 -0
- package/agents/engineering/eng.bug-hunter.md +386 -0
- package/agents/engineering/eng.cybersecurity.agent.md +503 -0
- package/agents/engineering/eng.dev-code-reviewer.md +148 -0
- package/agents/engineering/eng.docs-writer.md +152 -0
- package/agents/engineering/eng.frontend.agent.md +117 -0
- package/agents/engineering/eng.rpa.agent.md +215 -0
- package/agents/engineering/eng.tech-analyst.agent.md +102 -0
- package/agents/engineering/eng.ux-designer.agent.md +193 -0
- package/agents/engineering/qa/eng.qa.cypress-specialist.md +109 -0
- package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +85 -0
- package/agents/engineering/qa/eng.qa.quality-strategist.md +111 -0
- package/agents/engineering/qa/eng.qa.test-architect.md +400 -0
- package/agents/engineering/qa/eng.qa.test-planner.md +477 -0
- package/agents/engineering/qa/eng.qa.testing-engineer.md +339 -0
- package/agents/product/prod.pm-checker.md +52 -0
- package/bin/commands/docs-publish.js +184 -0
- package/bin/commands/docs-sync.js +139 -0
- package/bin/commands/info.js +87 -0
- package/bin/commands/init.js +237 -0
- package/bin/commands/install-rtk.js +90 -0
- package/bin/commands/list.js +48 -0
- package/bin/commands/qa-signoff.js +112 -0
- package/bin/commands/whoami.js +43 -0
- package/bin/jarvis.js +159 -0
- package/bin/lib/auth/session.js +56 -0
- package/bin/lib/config/constants.js +123 -0
- package/bin/lib/config/ide-config.js +233 -0
- package/bin/lib/core/scanner.js +124 -0
- package/bin/lib/core/sync-engine.js +551 -0
- package/bin/lib/docs/fetch-file.sh +41 -0
- package/bin/lib/docs/publish-file.sh +284 -0
- package/bin/lib/docs/validate-frontmatter.js +157 -0
- package/bin/lib/env-loader.js +198 -0
- package/bin/lib/tasks/comment.js +131 -0
- package/bin/lib/utils/git-parser.js +145 -0
- package/bin/lib/utils/logger.js +104 -0
- package/bin/lib/utils/npmrc-parser.js +106 -0
- package/bin/lib/utils/paths.js +55 -0
- package/bin/lib/utils/ui.js +59 -0
- package/bin/lib/vcs/api.js +312 -0
- package/bin/lib/vcs/create-issue.js +43 -0
- package/bin/lib/vcs/create-merge.js +43 -0
- package/bin/lib/vcs/fetch-raw.js +30 -0
- package/bin/postinstall.js +41 -0
- package/members.md +25 -0
- package/package.json +55 -0
- package/rules/AGENTS.md +205 -0
- package/rules/engineering/data/data-rules.md +200 -0
- package/rules/engineering/eng-rules.md +243 -0
- package/rules/engineering/eng-security-rules.md +186 -0
- package/rules/engineering/eng.breakdown-subtasks-rules.md +585 -0
- package/rules/engineering/eng.bump-rules.md +27 -0
- package/rules/engineering/eng.docs-scraping-rules.md +64 -0
- package/rules/engineering/eng.downstream-flow-rules.md +297 -0
- package/rules/engineering/eng.integrations-rules.md +73 -0
- package/rules/engineering/eng.plan-rules.md +333 -0
- package/rules/engineering/eng.pr-rules.md +359 -0
- package/rules/engineering/eng.pre-pr-rules.md +103 -0
- package/rules/engineering/eng.start-rules.md +246 -0
- package/rules/engineering/eng.tech-spec-rules.md +968 -0
- package/rules/engineering/eng.work-rules.md +312 -0
- package/rules/engineering/frontend/eng.frontend-rules.md +147 -0
- package/rules/engineering/qa/eng.qa.cypress-standards-rules.md +259 -0
- package/rules/engineering/qa/eng.qa.exploratory-session-rules.md +137 -0
- package/rules/engineering/qa/eng.qa.quality-gate-scoring-rules.md +181 -0
- package/rules/engineering/qa/eng.qa.tech-spec-validation-criteria-rules.md +120 -0
- package/rules/engineering/rpa/eng.rpa-rules.md +230 -0
- package/rules/product/README.md +24 -0
- package/rules/product/prod-rules.md +151 -0
- package/rules/rtk-rules.md +68 -0
- package/skills/AGENTS.md +290 -0
- package/skills/SKILLS-ROADMAP.md +333 -0
- package/skills/churn-audit/SKILL.md +385 -0
- package/skills/context-detect/SKILL.md +399 -0
- package/skills/context-detect/assets/context-profile-template.md +127 -0
- package/skills/docs-central/README.md +310 -0
- package/skills/docs-central/SKILL.md +423 -0
- package/skills/docs-index/SKILL.md +377 -0
- package/skills/eng-ai-engineer/SKILL.md +296 -0
- package/skills/eng-arch-c4/SKILL.md +358 -0
- package/skills/eng-arch-c4/assets/example-code.md +189 -0
- package/skills/eng-arch-c4/assets/example-component.md +105 -0
- package/skills/eng-arch-c4/assets/example-container.md +104 -0
- package/skills/eng-arch-c4/assets/example-context.md +81 -0
- package/skills/eng-backend/SKILL.md +776 -0
- package/skills/eng-browser-extension-builder/SKILL.md +385 -0
- package/skills/eng-cybersecurity/SKILL.md +645 -0
- package/skills/eng-data-bi/SKILL.md +199 -0
- package/skills/eng-data-debug/SKILL.md +307 -0
- package/skills/eng-data-engineer/SKILL.md +256 -0
- package/skills/eng-data-onboard/SKILL.md +310 -0
- package/skills/eng-data-orchestrator/SKILL.md +426 -0
- package/skills/eng-design-system/SKILL.md +619 -0
- package/skills/eng-docs-write/SKILL.md +312 -0
- package/skills/eng-frontend/SKILL.md +913 -0
- package/skills/eng-jira-comment/SKILL.md +17 -0
- package/skills/eng-microfrontend/SKILL.md +602 -0
- package/skills/eng-ms-trace/SKILL.md +469 -0
- package/skills/eng-nestjs/SKILL.md +791 -0
- package/skills/eng-performance-engineer/SKILL.md +312 -0
- package/skills/eng-pr/SKILL.md +339 -0
- package/skills/eng-qa-a11y-audit/SKILL.md +269 -0
- package/skills/eng-qa-bug-report/SKILL.md +1088 -0
- package/skills/eng-qa-bug-report/TASK_MANAGERS.md +138 -0
- package/skills/eng-qa-cypress-e2e/SKILL.md +177 -0
- package/skills/eng-qa-dev-guide/SKILL.md +164 -0
- package/skills/eng-qa-e2e/SKILL.md +400 -0
- package/skills/eng-qa-e2e-spec-writer/SKILL.md +322 -0
- package/skills/eng-qa-exploratory/SKILL.md +188 -0
- package/skills/eng-qa-gate/SKILL.md +370 -0
- package/skills/eng-qa-gate/assets/checklist-validacao.md +291 -0
- package/skills/eng-qa-graphql-contract/SKILL.md +256 -0
- package/skills/eng-qa-quality-report/SKILL.md +412 -0
- package/skills/eng-qa-test-plan/SKILL.md +466 -0
- package/skills/eng-qa-test-plan/assets/test-coverage-template.md +92 -0
- package/skills/eng-qa-test-plan/assets/test-patterns.md +178 -0
- package/skills/eng-qa-testsprite/SKILL.md +325 -0
- package/skills/eng-qa-testsprite/references/testsprite-mcp.md +224 -0
- package/skills/eng-qa-unit-test/SKILL.md +471 -0
- package/skills/eng-rabbitmq/SKILL.md +661 -0
- package/skills/eng-scraper/SKILL.md +683 -0
- package/skills/eng-scraper-robot-builder/SKILL.md +370 -0
- package/skills/eng-security-patch/SKILL.md +378 -0
- package/skills/eng-security-triage/SKILL.md +266 -0
- package/skills/eng-task-comment/SKILL.md +60 -0
- package/skills/eng-tech-analyst/SKILL.md +529 -0
- package/skills/eng-threat-model/SKILL.md +161 -0
- package/skills/init-jarvis/SKILL.md +1304 -0
- package/skills/init-jarvis/assets/mcp-configs.md +389 -0
- package/skills/init-jarvis/assets/onboarding-checklist.md +104 -0
- package/skills/init-jarvis/assets/setup-guide.md +360 -0
- package/skills/lovable-prompt-generator/SKILL.md +304 -0
- package/skills/prod-roadmap-report/README.md +303 -0
- package/skills/prod-roadmap-report/SKILL.md +198 -0
- package/skills/prod-roadmap-report/commands/status.compiled.single.team.md +23 -0
- package/skills/prod-roadmap-report/commands/status.list.projects.md +17 -0
- package/skills/prod-roadmap-report/commands/status.memory.md +192 -0
- package/skills/prod-roadmap-report/commands/status.roadmap.preview.md +94 -0
- package/skills/prod-roadmap-report/references/detailed-guide.md +236 -0
- package/skills/prod-roadmap-report/rules/detailed-guide.md +237 -0
- package/skills/prod-roadmap-report/rules/status-report-rules.md +44 -0
- package/skills/prod-roadmap-report/templates/template-multiple-teams-compiled-status.md +53 -0
- package/skills/prod-roadmap-report/templates/template-projects-list.md +23 -0
- package/skills/prod-roadmap-report/templates/template-single-team-compiled-status.md +60 -0
- package/skills/prod-roadmap-report/templates/template-single-team-status.md +49 -0
- package/skills/prod-specs/SKILL.md +108 -0
- package/skills/prod-specs/references/prod.spec.clarify.md +176 -0
- package/skills/prod-specs/references/prod.spec.epic.md +107 -0
- package/skills/prod-specs/references/prod.spec.frd.md +135 -0
- package/skills/prod-specs/references/prod.spec.issue.md +145 -0
- package/skills/prod-specs/references/prod.spec.prd.md +118 -0
- package/skills/prod-specs/rules/prod-spec-rules.md +186 -0
- package/skills/prod-specs/templates/prod-breakdown-template.md +136 -0
- package/skills/prod-specs/templates/prod-epic-template.md +76 -0
- package/skills/prod-specs/templates/prod-frd-template.md +172 -0
- package/skills/prod-specs/templates/prod-issue-template.md +68 -0
- package/skills/prod-specs/templates/prod-prd-full-template.md +159 -0
- package/skills/prod-specs/templates/prod-prd-template.md +173 -0
- package/skills/prod-specs-update/SKILL.md +272 -0
- package/skills/report-issue/SKILL.md +156 -0
- package/taxonomy.md +270 -0
- package/templates/AGENTS.md +189 -0
- package/templates/CDD aplicado a Prompts.md +182 -0
- package/templates/ENV-template.md +187 -0
- package/templates/engineering/AGENTS-template.md +71 -0
- package/templates/engineering/ARD-template.md +193 -0
- package/templates/engineering/CONTACTS-template.md +135 -0
- package/templates/engineering/PR-template.md +40 -0
- package/templates/engineering/RFC-Playbook.md +325 -0
- package/templates/engineering/RFC-template.md +199 -0
- package/templates/engineering/architecture-template.md +277 -0
- package/templates/engineering/breakdown-subtasks-template.md +582 -0
- package/templates/engineering/c4-model-template.md +516 -0
- package/templates/engineering/data-contract-template.md +135 -0
- package/templates/engineering/data-pipeline-template.md +163 -0
- package/templates/engineering/plan-template.md +255 -0
- package/templates/engineering/qa/eng.qa.quality-gate-examples-template.md +311 -0
- package/templates/engineering/qa/eng.qa.quality-gate-report-template.md +249 -0
- package/templates/engineering/qa/qa.cypress-test-template.md +172 -0
- package/templates/engineering/qa/qa.exploratory-session-template.md +148 -0
- package/templates/engineering/qa/qa.quality-report-template.md +130 -0
- package/templates/engineering/qa/qa.release-signoff-template.md +54 -0
- package/templates/engineering/qa/qa.sprint-plan-template.md +49 -0
- package/templates/engineering/swagger-template.md +145 -0
- package/templates/engineering/tech-spec-template.md +497 -0
- package/templates/engineering/work-progress-template.md +155 -0
- package/workflows/AGENTS.md +240 -0
- package/workflows/README.md +160 -0
- package/workflows/all-tools.md +11 -0
- package/workflows/engineering/data/data.contract.md +202 -0
- package/workflows/engineering/data/data.new-pipeline.md +234 -0
- package/workflows/engineering/eng.breakdown-subtasks.md +420 -0
- package/workflows/engineering/eng.bug-audit.md +591 -0
- package/workflows/engineering/eng.build-tech-spec.md +1116 -0
- package/workflows/engineering/eng.create-ard-from-code.md +259 -0
- package/workflows/engineering/eng.create-ard.md +382 -0
- package/workflows/engineering/eng.create-rfc.md +245 -0
- package/workflows/engineering/eng.debug.md +479 -0
- package/workflows/engineering/eng.docs.md +40 -0
- package/workflows/engineering/eng.light-arch.md +84 -0
- package/workflows/engineering/eng.plan.md +213 -0
- package/workflows/engineering/eng.pr.md +466 -0
- package/workflows/engineering/eng.pre-pr.md +167 -0
- package/workflows/engineering/eng.review.md +185 -0
- package/workflows/engineering/eng.rpa.robot.md +342 -0
- package/workflows/engineering/eng.security-audit.md +312 -0
- package/workflows/engineering/eng.security-incident.md +275 -0
- package/workflows/engineering/eng.security-pipeline.md +210 -0
- package/workflows/engineering/eng.security-review.md +235 -0
- package/workflows/engineering/eng.start.md +494 -0
- package/workflows/engineering/eng.work.md +558 -0
- package/workflows/engineering/frontend/eng.frontend-component.md +190 -0
- package/workflows/engineering/frontend/eng.frontend-perf-audit.md +375 -0
- package/workflows/engineering/frontend/eng.frontend-review.md +185 -0
- package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +51 -0
- package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +51 -0
- package/workflows/engineering/qa/eng.qa-exploratory-session.md +60 -0
- package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +202 -0
- package/workflows/engineering/qa/eng.qa-quality-report.md +83 -0
- package/workflows/engineering/qa/eng.qa-refinement-entry.md +83 -0
- package/workflows/engineering/qa/eng.qa-release-signoff.md +170 -0
- package/workflows/engineering/qa/eng.qa-sprint-planning.md +100 -0
- package/workflows/engineering/ta/eng.ta.atendimento.md +93 -0
- package/workflows/product/prod.roadmap.preview.md +110 -0
- package/workflows/product/prod.spec.breakdown.md +163 -0
- package/workflows/product/prod.spec.clarify.md +178 -0
- package/workflows/product/prod.spec.epic.md +154 -0
- package/workflows/product/prod.spec.frd.md +96 -0
- package/workflows/product/prod.spec.issue.md +145 -0
- package/workflows/product/prod.spec.md +60 -0
- package/workflows/product/prod.spec.prd.md +100 -0
- package/workflows/taxonomy.md +92 -0
- package/workflows/warm-up.md +574 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
cpSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
12
|
+
import { join, relative, basename } from "node:path";
|
|
13
|
+
import { getFrameworkRoot } from "../utils/paths.js";
|
|
14
|
+
import { getIDEFolder } from "../config/ide-config.js";
|
|
15
|
+
import {
|
|
16
|
+
SYNC_DIRS,
|
|
17
|
+
SYNC_ROOT_FILES,
|
|
18
|
+
LOCK_FILE,
|
|
19
|
+
OPENCODE_MODEL_MAP,
|
|
20
|
+
OPENCODE_DEFAULT_PERMISSIONS,
|
|
21
|
+
OPENCODE_STRIP_FIELDS,
|
|
22
|
+
KIRO_STRIP_FIELDS,
|
|
23
|
+
KIRO_HUB_FILE_PATTERNS,
|
|
24
|
+
} from "../config/constants.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extrai name e description do frontmatter YAML de um SKILL.md
|
|
28
|
+
* @param {string} content - Conteúdo do SKILL.md
|
|
29
|
+
* @returns {{ name: string, description: string } | null}
|
|
30
|
+
*/
|
|
31
|
+
function parseSkillFrontmatter(content) {
|
|
32
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
33
|
+
if (!match) return null;
|
|
34
|
+
const fm = match[1];
|
|
35
|
+
const name = (fm.match(/^name:\s*(.+)$/m) || [])[1]?.trim();
|
|
36
|
+
// description pode ser multiline com `>`
|
|
37
|
+
const descMatch = fm.match(/^description:\s*[>|]?\n?([\s\S]*?)(?=\n\S|\n---|\Z)/m);
|
|
38
|
+
const description = descMatch
|
|
39
|
+
? descMatch[1].replace(/^\s+/gm, "").replace(/\n+/g, " ").trim()
|
|
40
|
+
: "";
|
|
41
|
+
return name ? { name, description } : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gera openai.yaml para cada skill sincronizado ao Codex.
|
|
46
|
+
* O Codex CLI usa este arquivo para integrar skills no painel TUI.
|
|
47
|
+
* @param {string} skillsDir - Caminho para .codex/skills/
|
|
48
|
+
*/
|
|
49
|
+
function generateCodexOpenAIYaml(skillsDir) {
|
|
50
|
+
if (!existsSync(skillsDir)) return;
|
|
51
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
52
|
+
if (!entry.isDirectory()) continue;
|
|
53
|
+
const skillDir = join(skillsDir, entry.name);
|
|
54
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
55
|
+
const yamlPath = join(skillDir, "openai.yaml");
|
|
56
|
+
if (!existsSync(skillMdPath) || existsSync(yamlPath)) continue;
|
|
57
|
+
const content = readFileSync(skillMdPath, "utf-8");
|
|
58
|
+
const parsed = parseSkillFrontmatter(content);
|
|
59
|
+
if (!parsed) continue;
|
|
60
|
+
const yaml =
|
|
61
|
+
`name: ${parsed.name}\n` +
|
|
62
|
+
`description: "${parsed.description.replace(/"/g, '\\"')}"\n`;
|
|
63
|
+
writeFileSync(yamlPath, yaml, "utf-8");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function syncAssets(targetDir, ide, opts = {}) {
|
|
68
|
+
const frameworkRoot = getFrameworkRoot();
|
|
69
|
+
const ideDir = join(targetDir, `.${getIDEFolder(ide)}`);
|
|
70
|
+
const result = { copied: [], skipped: [], errors: [] };
|
|
71
|
+
|
|
72
|
+
if (!opts.dryRun) {
|
|
73
|
+
mkdirSync(ideDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const dir of SYNC_DIRS) {
|
|
77
|
+
const src = join(frameworkRoot, dir);
|
|
78
|
+
// Se for "workflows" e a IDE usa outro nome, usa o nome configurado
|
|
79
|
+
const destDirName = (dir === "workflows" && opts.workflowsFolder)
|
|
80
|
+
? opts.workflowsFolder
|
|
81
|
+
: dir;
|
|
82
|
+
const dest = join(ideDir, destDirName);
|
|
83
|
+
|
|
84
|
+
if (!existsSync(src)) {
|
|
85
|
+
result.skipped.push(`${dir}/ (not found in framework)`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
if (!opts.dryRun) {
|
|
91
|
+
cpSync(src, dest, { recursive: true, force: opts.force ?? true });
|
|
92
|
+
}
|
|
93
|
+
result.copied.push(`${destDirName}/`);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
result.errors.push(`${destDirName}/: ${err.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Copy root files to IDE folder
|
|
100
|
+
for (const file of SYNC_ROOT_FILES) {
|
|
101
|
+
const src = join(frameworkRoot, file);
|
|
102
|
+
const dest = join(ideDir, file);
|
|
103
|
+
|
|
104
|
+
if (!existsSync(src)) continue;
|
|
105
|
+
|
|
106
|
+
if (existsSync(dest) && !opts.force) {
|
|
107
|
+
result.skipped.push(`${file} (already exists)`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
if (!opts.dryRun) {
|
|
113
|
+
cpSync(src, dest);
|
|
114
|
+
}
|
|
115
|
+
result.copied.push(file);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
result.errors.push(`${file}: ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Flatten workflows subfolders — todas as IDEs requerem arquivos na raiz sem subpastas
|
|
122
|
+
const workflowsDest = join(ideDir, opts.workflowsFolder || "workflows");
|
|
123
|
+
if (existsSync(workflowsDest)) {
|
|
124
|
+
const moved = flattenDir(workflowsDest);
|
|
125
|
+
if (moved > 0) result.copied.push(`(flatten) ${moved} workflows movidos para raiz`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Cleanup leftover metrics skill copies from older installs
|
|
129
|
+
const legacyMetricsDir = join(ideDir, "metrics");
|
|
130
|
+
if (existsSync(legacyMetricsDir) && !opts.dryRun) {
|
|
131
|
+
try {
|
|
132
|
+
rmSync(legacyMetricsDir, { recursive: true, force: true });
|
|
133
|
+
result.copied.push(`(cleanup) .${ide}/metrics/ removed`);
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!opts.dryRun) {
|
|
138
|
+
writeLockFile(targetDir, ideDir, ide, frameworkRoot);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// RTK: detectar e reconciliar RTK_ENABLED + rtk-rules.md
|
|
142
|
+
if (!opts.dryRun) {
|
|
143
|
+
const rtkResult = reconcileRtk(ideDir, targetDir, ide);
|
|
144
|
+
if (rtkResult) result.copied.push(rtkResult);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Codex: gerar openai.yaml por skill para integração no painel TUI
|
|
148
|
+
if (ide === "codex" && !opts.dryRun) {
|
|
149
|
+
const codexSkillsDir = join(ideDir, "skills");
|
|
150
|
+
generateCodexOpenAIYaml(codexSkillsDir);
|
|
151
|
+
result.copied.push("(codex) openai.yaml gerado por skill em skills/*/");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Kiro: gerar steering files (workflows já estão em steering/, processar rules e skills)
|
|
155
|
+
if (ide === "kiro" && !opts.dryRun) {
|
|
156
|
+
const kiroDir = ideDir;
|
|
157
|
+
transformKiroAssets(kiroDir, targetDir);
|
|
158
|
+
result.copied.push("(kiro) steering files gerados para workflows, rules e skills");
|
|
159
|
+
|
|
160
|
+
generateKiroMcpConfig(targetDir, kiroDir);
|
|
161
|
+
result.copied.push("(kiro) .kiro/settings/mcp.json verificado");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// OpenCode: transformar frontmatter de agents e commands + gerar opencode.json
|
|
165
|
+
if (ide === "opencode" && !opts.dryRun) {
|
|
166
|
+
const ocAgentsDir = join(ideDir, "agents");
|
|
167
|
+
transformOpenCodeAgents(ocAgentsDir);
|
|
168
|
+
result.copied.push("(opencode) agents transformados para formato OpenCode");
|
|
169
|
+
|
|
170
|
+
const ocCommandsDir = join(ideDir, opts.workflowsFolder || "commands");
|
|
171
|
+
transformOpenCodeCommands(ocCommandsDir);
|
|
172
|
+
result.copied.push("(opencode) commands transformados para formato OpenCode");
|
|
173
|
+
|
|
174
|
+
generateOpenCodeConfig(targetDir);
|
|
175
|
+
result.copied.push("(opencode) opencode.json gerado na raiz");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Detecta RTK no PATH e reconcilia RTK_ENABLED no ENV.md + rtk-rules.md na IDE.
|
|
183
|
+
* - RTK encontrado → RTK_ENABLED=true no ENV.md, mantém rule
|
|
184
|
+
* - RTK não encontrado → remove rtk-rules.md da IDE, mantém RTK_ENABLED=false
|
|
185
|
+
* @returns {string|null} mensagem de log ou null se nenhuma ação
|
|
186
|
+
*/
|
|
187
|
+
function reconcileRtk(ideDir, targetDir, ide) {
|
|
188
|
+
const rtkRulePath = join(ideDir, "rules", "rtk-rules.md");
|
|
189
|
+
const envPath = join(ideDir, "ENV.md");
|
|
190
|
+
const hasRtk = (() => {
|
|
191
|
+
try {
|
|
192
|
+
execSync("which rtk 2>/dev/null", { stdio: "pipe" });
|
|
193
|
+
return true;
|
|
194
|
+
} catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
|
|
199
|
+
if (hasRtk) {
|
|
200
|
+
// Ativar RTK_ENABLED no ENV.md se existir e estiver false
|
|
201
|
+
if (existsSync(envPath)) {
|
|
202
|
+
let env = readFileSync(envPath, "utf-8");
|
|
203
|
+
if (env.includes("RTK_ENABLED=false")) {
|
|
204
|
+
env = env.replace("RTK_ENABLED=false", "RTK_ENABLED=true");
|
|
205
|
+
writeFileSync(envPath, env, "utf-8");
|
|
206
|
+
return "(rtk) detectado — RTK_ENABLED=true no ENV.md";
|
|
207
|
+
}
|
|
208
|
+
if (!env.includes("RTK_ENABLED")) {
|
|
209
|
+
env = env.trimEnd() + "\n\n# --- RTK — Token Killer ---\nRTK_ENABLED=true\n";
|
|
210
|
+
writeFileSync(envPath, env, "utf-8");
|
|
211
|
+
return "(rtk) detectado — RTK_ENABLED=true adicionado ao ENV.md";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return null; // já estava true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// RTK não encontrado — remover rule se foi copiada
|
|
218
|
+
if (existsSync(rtkRulePath)) {
|
|
219
|
+
rmSync(rtkRulePath);
|
|
220
|
+
return "(rtk) não detectado — rtk-rules.md removida de rules/";
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Resolve o model Jarvis para formato OpenCode (provider/model-id).
|
|
227
|
+
* @param {string} model - Model no formato Jarvis (ex: "opus", "claude-sonnet-4-20250514")
|
|
228
|
+
* @returns {string} Model no formato OpenCode (ex: "anthropic/claude-opus-4-6")
|
|
229
|
+
*/
|
|
230
|
+
function resolveOpenCodeModel(model) {
|
|
231
|
+
if (!model) return "anthropic/claude-sonnet-4-6";
|
|
232
|
+
const trimmed = model.trim();
|
|
233
|
+
if (trimmed.includes("/")) return trimmed; // já no formato provider/model
|
|
234
|
+
if (OPENCODE_MODEL_MAP[trimmed]) return OPENCODE_MODEL_MAP[trimmed];
|
|
235
|
+
// Inferir provider pelo prefixo do modelo
|
|
236
|
+
if (trimmed.startsWith("claude")) return `anthropic/${trimmed}`;
|
|
237
|
+
if (trimmed.startsWith("gpt") || trimmed.startsWith("o3") || trimmed.startsWith("o4")) return `openai/${trimmed}`;
|
|
238
|
+
if (trimmed.startsWith("gemini")) return `google/${trimmed}`;
|
|
239
|
+
// Fallback: retornar sem provider — OpenCode resolverá pelo provider padrão
|
|
240
|
+
return trimmed;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Transforma frontmatter de agents Jarvis para formato OpenCode.
|
|
245
|
+
* Reescreve os .md já copiados em .opencode/agents/.
|
|
246
|
+
* @param {string} agentsDir - Caminho para .opencode/agents/
|
|
247
|
+
*/
|
|
248
|
+
function transformOpenCodeAgents(agentsDir) {
|
|
249
|
+
if (!existsSync(agentsDir)) return;
|
|
250
|
+
const files = [];
|
|
251
|
+
collectMdFiles(agentsDir, files);
|
|
252
|
+
|
|
253
|
+
for (const filePath of files) {
|
|
254
|
+
const content = readFileSync(filePath, "utf-8");
|
|
255
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
256
|
+
if (!fmMatch) continue;
|
|
257
|
+
|
|
258
|
+
const fm = fmMatch[1];
|
|
259
|
+
const body = content.slice(fmMatch[0].length).trim();
|
|
260
|
+
|
|
261
|
+
// Extract fields from Jarvis frontmatter
|
|
262
|
+
const description = (fm.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || "";
|
|
263
|
+
const model = (fm.match(/^model:\s*(.+)$/m) || [])[1]?.trim();
|
|
264
|
+
const name = basename(filePath, ".md");
|
|
265
|
+
const isMainAgent = name === "eng.agent" || name === "eng-agent";
|
|
266
|
+
const mode = isMainAgent ? "primary" : "subagent";
|
|
267
|
+
|
|
268
|
+
// Build OpenCode frontmatter
|
|
269
|
+
const lines = [`description: "${description.replace(/"/g, '\\"')}"`];
|
|
270
|
+
lines.push(`mode: ${mode}`);
|
|
271
|
+
lines.push(`model: ${resolveOpenCodeModel(model)}`);
|
|
272
|
+
lines.push("permission:");
|
|
273
|
+
for (const [key, val] of Object.entries(OPENCODE_DEFAULT_PERMISSIONS)) {
|
|
274
|
+
lines.push(` ${key}: ${val}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const newContent = `---\n${lines.join("\n")}\n---\n\n${body}\n`;
|
|
278
|
+
writeFileSync(filePath, newContent, "utf-8");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Transforma frontmatter de commands (workflows) Jarvis para formato OpenCode.
|
|
284
|
+
* Remove campos Jarvis-only, converte model e agent path para nome.
|
|
285
|
+
* @param {string} commandsDir - Caminho para .opencode/commands/
|
|
286
|
+
*/
|
|
287
|
+
function transformOpenCodeCommands(commandsDir) {
|
|
288
|
+
if (!existsSync(commandsDir)) return;
|
|
289
|
+
const files = [];
|
|
290
|
+
collectMdFiles(commandsDir, files);
|
|
291
|
+
|
|
292
|
+
for (const filePath of files) {
|
|
293
|
+
const content = readFileSync(filePath, "utf-8");
|
|
294
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
295
|
+
if (!fmMatch) continue;
|
|
296
|
+
|
|
297
|
+
const fm = fmMatch[1];
|
|
298
|
+
const body = content.slice(fmMatch[0].length).trim();
|
|
299
|
+
|
|
300
|
+
// Extract fields
|
|
301
|
+
const description = (fm.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || "";
|
|
302
|
+
const model =
|
|
303
|
+
(fm.match(/^recommended_model:\s*(.+)$/m) || [])[1]?.trim() ||
|
|
304
|
+
(fm.match(/^model:\s*(.+)$/m) || [])[1]?.trim();
|
|
305
|
+
|
|
306
|
+
// Convert agent path to agent name: $IDE/agents/engineering/eng.agent.md → eng.agent
|
|
307
|
+
const agentMatch = fm.match(/^agent:\s*["']?(.+?)["']?\s*$/m);
|
|
308
|
+
let agentName = "";
|
|
309
|
+
if (agentMatch) {
|
|
310
|
+
const agentPath = agentMatch[1].trim();
|
|
311
|
+
agentName = basename(agentPath, ".md");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Build OpenCode command frontmatter (only supported fields)
|
|
315
|
+
const lines = [`description: "${description.replace(/"/g, '\\"')}"`];
|
|
316
|
+
if (agentName) lines.push(`agent: ${agentName}`);
|
|
317
|
+
if (model) lines.push(`model: ${resolveOpenCodeModel(model)}`);
|
|
318
|
+
|
|
319
|
+
const newContent = `---\n${lines.join("\n")}\n---\n\n${body}\n`;
|
|
320
|
+
writeFileSync(filePath, newContent, "utf-8");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Gera opencode.json na raiz do projeto se não existir.
|
|
326
|
+
* @param {string} targetDir - Raiz do projeto alvo
|
|
327
|
+
*/
|
|
328
|
+
function generateOpenCodeConfig(targetDir) {
|
|
329
|
+
const configPath = join(targetDir, "opencode.json");
|
|
330
|
+
if (existsSync(configPath)) return; // não sobrescrever config do usuário
|
|
331
|
+
|
|
332
|
+
const config = {
|
|
333
|
+
$schema: "https://opencode.ai/config.json",
|
|
334
|
+
model: "anthropic/claude-sonnet-4-6",
|
|
335
|
+
instructions: [".opencode/AGENTS.md", ".opencode/rules/**/*.md"],
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Coleta recursivamente todos os .md de um diretório.
|
|
343
|
+
* @param {string} dir
|
|
344
|
+
* @param {string[]} acc
|
|
345
|
+
*/
|
|
346
|
+
function collectMdFiles(dir, acc) {
|
|
347
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
348
|
+
const full = join(dir, entry.name);
|
|
349
|
+
if (entry.isDirectory()) collectMdFiles(full, acc);
|
|
350
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) acc.push(full);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function flattenDir(dir, rootDir) {
|
|
355
|
+
const root = rootDir ?? dir;
|
|
356
|
+
let moved = 0;
|
|
357
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
358
|
+
const fullPath = join(dir, entry.name);
|
|
359
|
+
if (entry.isDirectory()) {
|
|
360
|
+
moved += flattenDir(fullPath, root);
|
|
361
|
+
try { rmSync(fullPath, { recursive: true, force: true }); } catch {}
|
|
362
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
363
|
+
const dest = join(root, basename(entry.name));
|
|
364
|
+
if (fullPath !== dest) {
|
|
365
|
+
try { renameSync(fullPath, dest); moved++; } catch {}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return moved;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Converte o frontmatter de um arquivo .md para um steering file Kiro.
|
|
374
|
+
* - Remove campos Jarvis-only (KIRO_STRIP_FIELDS)
|
|
375
|
+
* - Adiciona `inclusion` se não presente
|
|
376
|
+
* - Preserva `name` e `description`
|
|
377
|
+
* @param {string} content - Conteúdo original do .md
|
|
378
|
+
* @param {string} inclusion - Valor de inclusion: "auto" | "manual" | "fileMatch"
|
|
379
|
+
* @param {string} [fileMatchPattern] - Pattern para inclusion: fileMatch
|
|
380
|
+
* @returns {string} Conteúdo transformado
|
|
381
|
+
*/
|
|
382
|
+
function toKiroSteeringFile(content, inclusion, fileMatchPattern) {
|
|
383
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
384
|
+
const body = fmMatch ? content.slice(fmMatch[0].length).trimStart() : content;
|
|
385
|
+
|
|
386
|
+
const lines = [`inclusion: ${inclusion}`];
|
|
387
|
+
if (inclusion === "fileMatch" && fileMatchPattern) {
|
|
388
|
+
lines.push(`fileMatchPattern: "${fileMatchPattern}"`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (fmMatch) {
|
|
392
|
+
const fm = fmMatch[1];
|
|
393
|
+
const description = (fm.match(/^description:\s*[>|]?\s*\n?([\s\S]*?)(?=\n\S|\n---|\Z)/m) || [])[1];
|
|
394
|
+
const descInline = (fm.match(/^description:\s*(.+)$/m) || [])[1];
|
|
395
|
+
const finalDesc = (description || descInline || "").replace(/^\s+/gm, "").replace(/\n+/g, " ").trim();
|
|
396
|
+
if (finalDesc) lines.push(`description: "${finalDesc.replace(/"/g, '\\"')}"`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return `---\n${lines.join("\n")}\n---\n\n${body}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Detecta o HUB de uma rule a partir do bloco "Applies to" no corpo.
|
|
404
|
+
* Retorna o HUB em maiúsculas ou null se não encontrado.
|
|
405
|
+
* @param {string} content
|
|
406
|
+
* @returns {string | null}
|
|
407
|
+
*/
|
|
408
|
+
function detectRuleHub(content) {
|
|
409
|
+
const match = content.match(/\*\*Applies to:\*\*[^\n]*HUB:\s*([A-Z-]+)/i);
|
|
410
|
+
if (!match) return null;
|
|
411
|
+
const hub = match[1].trim().toUpperCase();
|
|
412
|
+
return hub === "ALL" ? null : hub;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Transforma os assets copiados para .kiro/ em steering files.
|
|
417
|
+
* 1. Workflows em steering/ → adiciona inclusion: manual
|
|
418
|
+
* 2. Rules em rules/ → copia para steering/ com inclusion: auto ou fileMatch
|
|
419
|
+
* 3. Skills em skills/*\/SKILL.md → copia para steering/skills/skill-{nome}.md
|
|
420
|
+
* @param {string} kiroDir - Caminho para .kiro/
|
|
421
|
+
* @param {string} targetDir - Raiz do projeto (para buscar MCPs.md)
|
|
422
|
+
*/
|
|
423
|
+
function transformKiroAssets(kiroDir, targetDir) {
|
|
424
|
+
const steeringDir = join(kiroDir, "steering");
|
|
425
|
+
mkdirSync(steeringDir, { recursive: true });
|
|
426
|
+
|
|
427
|
+
// 1. Workflows — já estão em steering/ (copiados com workflowsFolder="steering")
|
|
428
|
+
// Adicionar `inclusion: manual` no frontmatter de cada arquivo
|
|
429
|
+
if (existsSync(steeringDir)) {
|
|
430
|
+
for (const entry of readdirSync(steeringDir, { withFileTypes: true })) {
|
|
431
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
432
|
+
const filePath = join(steeringDir, entry.name);
|
|
433
|
+
const content = readFileSync(filePath, "utf-8");
|
|
434
|
+
const transformed = toKiroSteeringFile(content, "manual");
|
|
435
|
+
writeFileSync(filePath, transformed, "utf-8");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// 2. Rules — copiar de rules/ para steering/ com inclusion adequada
|
|
440
|
+
const rulesDir = join(kiroDir, "rules");
|
|
441
|
+
if (existsSync(rulesDir)) {
|
|
442
|
+
const ruleFiles = [];
|
|
443
|
+
collectMdFiles(rulesDir, ruleFiles);
|
|
444
|
+
|
|
445
|
+
for (const filePath of ruleFiles) {
|
|
446
|
+
const name = basename(filePath);
|
|
447
|
+
// Ignorar arquivos de índice/meta
|
|
448
|
+
if (name === "AGENTS.md" || name === "README.md") continue;
|
|
449
|
+
|
|
450
|
+
const content = readFileSync(filePath, "utf-8");
|
|
451
|
+
const hub = detectRuleHub(content);
|
|
452
|
+
const pattern = hub ? KIRO_HUB_FILE_PATTERNS[hub] : null;
|
|
453
|
+
const inclusion = pattern ? "fileMatch" : "auto";
|
|
454
|
+
const transformed = toKiroSteeringFile(content, inclusion, pattern);
|
|
455
|
+
|
|
456
|
+
writeFileSync(join(steeringDir, name), transformed, "utf-8");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 3. Skills — copiar SKILL.md de cada skill para steering/skills/skill-{nome}.md
|
|
461
|
+
const skillsDir = join(kiroDir, "skills");
|
|
462
|
+
const steeringSkillsDir = join(steeringDir, "skills");
|
|
463
|
+
if (existsSync(skillsDir)) {
|
|
464
|
+
mkdirSync(steeringSkillsDir, { recursive: true });
|
|
465
|
+
|
|
466
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
467
|
+
if (!entry.isDirectory()) continue;
|
|
468
|
+
const skillName = entry.name;
|
|
469
|
+
const skillMdPath = join(skillsDir, skillName, "SKILL.md");
|
|
470
|
+
if (!existsSync(skillMdPath)) continue;
|
|
471
|
+
|
|
472
|
+
const content = readFileSync(skillMdPath, "utf-8");
|
|
473
|
+
const transformed = toKiroSteeringFile(content, "manual");
|
|
474
|
+
writeFileSync(join(steeringSkillsDir, `skill-${skillName}.md`), transformed, "utf-8");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Gera .kiro/settings/mcp.json a partir de MCPs.md do projeto (se existir).
|
|
481
|
+
* Busca em $IDE/MCPs.md (qualquer IDE detectada no targetDir).
|
|
482
|
+
* Não sobrescreve se o arquivo já existir.
|
|
483
|
+
* @param {string} targetDir - Raiz do projeto alvo
|
|
484
|
+
* @param {string} kiroDir - Caminho para .kiro/
|
|
485
|
+
*/
|
|
486
|
+
function generateKiroMcpConfig(targetDir, kiroDir) {
|
|
487
|
+
const settingsDir = join(kiroDir, "settings");
|
|
488
|
+
const mcpJsonPath = join(settingsDir, "mcp.json");
|
|
489
|
+
if (existsSync(mcpJsonPath)) return;
|
|
490
|
+
|
|
491
|
+
// Buscar MCPs.md em qualquer pasta de IDE do projeto
|
|
492
|
+
const ideFolders = ["windsurf", "claude", "cursor", "codex", "opencode", "agents"];
|
|
493
|
+
let mcpsContent = null;
|
|
494
|
+
for (const folder of ideFolders) {
|
|
495
|
+
const candidate = join(targetDir, `.${folder}`, "MCPs.md");
|
|
496
|
+
if (existsSync(candidate)) {
|
|
497
|
+
mcpsContent = readFileSync(candidate, "utf-8");
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
503
|
+
|
|
504
|
+
if (!mcpsContent) {
|
|
505
|
+
// Gerar estrutura base vazia para o usuário preencher
|
|
506
|
+
const base = { mcpServers: {} };
|
|
507
|
+
writeFileSync(mcpJsonPath, JSON.stringify(base, null, 2) + "\n", "utf-8");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Extrair blocos de código JSON do MCPs.md (```json ... ```)
|
|
512
|
+
const blocks = [];
|
|
513
|
+
for (const m of mcpsContent.matchAll(/```json\n([\s\S]*?)```/g)) {
|
|
514
|
+
try {
|
|
515
|
+
const parsed = JSON.parse(m[1].trim());
|
|
516
|
+
if (parsed && typeof parsed === "object") blocks.push(parsed);
|
|
517
|
+
} catch {
|
|
518
|
+
// ignorar blocos inválidos
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const mcpServers = {};
|
|
523
|
+
for (const block of blocks) {
|
|
524
|
+
if (block.mcpServers) Object.assign(mcpServers, block.mcpServers);
|
|
525
|
+
else if (block.name && block.command) mcpServers[block.name] = block;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
writeFileSync(mcpJsonPath, JSON.stringify({ mcpServers }, null, 2) + "\n", "utf-8");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function writeLockFile(targetDir, ideDir, ide, frameworkRoot) {
|
|
532
|
+
const pkgPath = join(frameworkRoot, "package.json");
|
|
533
|
+
const pkg = existsSync(pkgPath)
|
|
534
|
+
? JSON.parse(readFileSync(pkgPath, "utf-8"))
|
|
535
|
+
: { version: "0.0.0" };
|
|
536
|
+
|
|
537
|
+
const lock = {
|
|
538
|
+
version: pkg.version,
|
|
539
|
+
ide,
|
|
540
|
+
syncedAt: new Date().toISOString(),
|
|
541
|
+
frameworkRoot: relative(targetDir, frameworkRoot) || ".",
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const lockContent = JSON.stringify(lock, null, 2) + "\n";
|
|
545
|
+
|
|
546
|
+
// Write lock file to project root (for postinstall.js)
|
|
547
|
+
writeFileSync(join(targetDir, LOCK_FILE), lockContent);
|
|
548
|
+
|
|
549
|
+
// Also write lock file to IDE folder
|
|
550
|
+
writeFileSync(join(ideDir, LOCK_FILE), lockContent);
|
|
551
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Busca arquivo do central-docs (GitLab / GitHub / Bitbucket) + cache Redis
|
|
3
|
+
|
|
4
|
+
FILE_PATH="$1"
|
|
5
|
+
REF="${2:-main}"
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
|
|
9
|
+
if [ -z "$FILE_PATH" ]; then
|
|
10
|
+
echo "Uso: fetch-file.sh <file-path> [ref]" >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if [ -z "$CENTRAL_DOCS_REPO" ]; then
|
|
15
|
+
echo "Erro: CENTRAL_DOCS_REPO não definido no ENV.md" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
CACHE_KEY="docs:${FILE_PATH}:${REF}"
|
|
20
|
+
CACHED=$(redis-cli GET "$CACHE_KEY" 2>/dev/null || echo "")
|
|
21
|
+
|
|
22
|
+
if [ -n "$CACHED" ]; then
|
|
23
|
+
echo "$CACHED"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
ERR_FILE=$(mktemp)
|
|
28
|
+
RESPONSE=$(node "${SCRIPT_DIR}/../vcs/fetch-raw.js" "$FILE_PATH" "$REF" 2>"$ERR_FILE")
|
|
29
|
+
EXIT_CODE=$?
|
|
30
|
+
|
|
31
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
32
|
+
TTL="${CENTRAL_DOCS_CACHE_TTL:-3600}"
|
|
33
|
+
echo "$RESPONSE" | redis-cli -x SETEX "$CACHE_KEY" "$TTL" >/dev/null 2>&1 || true
|
|
34
|
+
rm -f "$ERR_FILE"
|
|
35
|
+
echo "$RESPONSE"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
cat "$ERR_FILE" >&2
|
|
40
|
+
rm -f "$ERR_FILE"
|
|
41
|
+
exit $EXIT_CODE
|