@pathbit/agentdevkit 0.0.0-stage → 0.0.6
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/.pbai4devs/COMPATIBILIDADE.md +109 -0
- package/.pbai4devs/INSTRUCTIONS.md +87 -0
- package/.pbai4devs/README.md +53 -0
- package/.pbai4devs/agents/README.md +18 -0
- package/.pbai4devs/agents/revisor/AGENT.md +16 -0
- package/.pbai4devs/agents/revisor/antigravity.yaml +8 -0
- package/.pbai4devs/agents/revisor/claude.yaml +5 -0
- package/.pbai4devs/agents/revisor/cursor.yaml +5 -0
- package/.pbai4devs/agents/revisor/gemini.yaml +6 -0
- package/.pbai4devs/agents/revisor/github.yaml +5 -0
- package/.pbai4devs/agents/revisor/kimi-code.yaml +5 -0
- package/.pbai4devs/agents/revisor/mimocode.yaml +10 -0
- package/.pbai4devs/agents/revisor/opencode.yaml +7 -0
- package/.pbai4devs/agents/revisor/zcode.yaml +5 -0
- package/.pbai4devs/commands/README.md +30 -0
- package/.pbai4devs/commands/atualizar-ferramentas/COMMAND.md +17 -0
- package/.pbai4devs/commands/diagnosticar/COMMAND.md +18 -0
- package/.pbai4devs/commands/instalar-ferramentas/COMMAND.md +21 -0
- package/.pbai4devs/commands/preparar-dev/COMMAND.md +14 -0
- package/.pbai4devs/commands/proximos-passos/COMMAND.md +9 -0
- package/.pbai4devs/commands/revisar-entrega/COMMAND.md +29 -0
- package/.pbai4devs/commands/sincronizar/COMMAND.md +20 -0
- package/.pbai4devs/commands/status-roadmap/COMMAND.md +12 -0
- package/.pbai4devs/commands/testar-navegador/COMMAND.md +16 -0
- package/.pbai4devs/commands/verificar-agentes/COMMAND.md +19 -0
- package/.pbai4devs/estado/README.md +12 -0
- package/.pbai4devs/external-skills/CATALOG.json +121 -0
- package/.pbai4devs/external-skills/README.md +48 -0
- package/.pbai4devs/rules/README.md +18 -0
- package/.pbai4devs/rules/ambiente-local/RULE.md +33 -0
- package/.pbai4devs/rules/ambiente-local/cursor.yaml +4 -0
- package/.pbai4devs/rules/documentacao/RULE.md +11 -0
- package/.pbai4devs/rules/documentacao/cursor.yaml +4 -0
- package/.pbai4devs/rules/estrutura/RULE.md +33 -0
- package/.pbai4devs/rules/estrutura/cursor.yaml +4 -0
- package/.pbai4devs/rules/git/RULE.md +18 -0
- package/.pbai4devs/rules/git/cursor.yaml +4 -0
- package/.pbai4devs/rules/proximos-passos/RULE.md +14 -0
- package/.pbai4devs/rules/qualidade/RULE.md +32 -0
- package/.pbai4devs/rules/qualidade/cursor.yaml +4 -0
- package/.pbai4devs/scripts/README.md +69 -0
- package/.pbai4devs/scripts/diagnosticar-ambiente/SCRIPT.mjs +41 -0
- package/.pbai4devs/scripts/diagnosticar-ambiente/tests/TEST.mjs +30 -0
- package/.pbai4devs/scripts/executar-script/README.md +28 -0
- package/.pbai4devs/scripts/executar-script/SCRIPT.mjs +78 -0
- package/.pbai4devs/scripts/executar-script/tests/TEST.mjs +122 -0
- package/.pbai4devs/scripts/gerenciar-skills-externas/README.md +31 -0
- package/.pbai4devs/scripts/gerenciar-skills-externas/SCRIPT.mjs +182 -0
- package/.pbai4devs/scripts/gerenciar-skills-externas/tests/TEST.mjs +134 -0
- package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +190 -0
- package/.pbai4devs/scripts/inspecionar-projeto/tests/TEST.mjs +161 -0
- package/.pbai4devs/scripts/lib/README.md +9 -0
- package/.pbai4devs/scripts/lib/adapters.mjs +135 -0
- package/.pbai4devs/scripts/lib/catalog.mjs +170 -0
- package/.pbai4devs/scripts/lib/gitignore.mjs +348 -0
- package/.pbai4devs/scripts/lib/npm.mjs +17 -0
- package/.pbai4devs/scripts/lib/resource-path.mjs +20 -0
- package/.pbai4devs/scripts/lib/runtime.mjs +28 -0
- package/.pbai4devs/scripts/lib/script-config.mjs +8 -0
- package/.pbai4devs/scripts/preparar-ferramentas/BOOTSTRAP.md +34 -0
- package/.pbai4devs/scripts/preparar-ferramentas/BOOTSTRAP.mjs +189 -0
- package/.pbai4devs/scripts/preparar-ferramentas/README.md +71 -0
- package/.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs +455 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/FIXTURE.html +20 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_BROWSER.mjs +55 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_DESKTOP.mjs +61 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_VERCEL.mjs +192 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/TEST.mjs +552 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/bootstrap.test.mjs +94 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash/input.txt +2 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash-overlay/input.txt +1 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/skills/agentdevkit-smoke/SKILL.md +8 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/webreel/index.html +6 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/webreel/webreel.config.json +10 -0
- package/.pbai4devs/scripts/revisar-entrega/README.md +36 -0
- package/.pbai4devs/scripts/revisar-entrega/SCRIPT.mjs +249 -0
- package/.pbai4devs/scripts/revisar-entrega/tests/TEST.mjs +249 -0
- package/.pbai4devs/scripts/sincronizar-agentes/README.md +41 -0
- package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +473 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/LOCAL_IGNORE.mjs +290 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +869 -0
- package/.pbai4devs/scripts/testar-infraestrutura/SCRIPT.mjs +39 -0
- package/.pbai4devs/scripts/testar-infraestrutura/tests/TEST.mjs +29 -0
- package/.pbai4devs/scripts/testar-maturidade/README.md +54 -0
- package/.pbai4devs/scripts/testar-maturidade/SCRIPT.mjs +477 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/calculator.mjs +1 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/calculator.test.mjs.template +8 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/http-api.test.mjs.template +28 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/server.mjs +34 -0
- package/.pbai4devs/scripts/testar-maturidade/tests/TEST.mjs +305 -0
- package/.pbai4devs/scripts/testar-navegador/REFERENCE.md +49 -0
- package/.pbai4devs/scripts/testar-navegador/SCRIPT.conf +1 -0
- package/.pbai4devs/scripts/testar-navegador/SCRIPT.js +64 -0
- package/.pbai4devs/scripts/testar-navegador/tests/FAIL.js +8 -0
- package/.pbai4devs/scripts/testar-navegador/tests/SUCCESS.js +13 -0
- package/.pbai4devs/scripts/verificar-recursos/README.md +25 -0
- package/.pbai4devs/scripts/verificar-recursos/SCRIPT.mjs +209 -0
- package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +310 -0
- package/.pbai4devs/scripts/verificar-recursos/tests/ambiente-local.mjs +80 -0
- package/.pbai4devs/skills/README.md +24 -0
- package/.pbai4devs/skills/git/git-branch/SKILL.md +11 -0
- package/.pbai4devs/skills/git/git-commit/SKILL.md +16 -0
- package/.pbai4devs/skills/git/git-diff/SKILL.md +9 -0
- package/.pbai4devs/skills/git/git-fetch/SKILL.md +10 -0
- package/.pbai4devs/skills/git/git-log/SKILL.md +9 -0
- package/.pbai4devs/skills/git/git-merge/SKILL.md +13 -0
- package/.pbai4devs/skills/git/git-pull/SKILL.md +12 -0
- package/.pbai4devs/skills/git/git-push/SKILL.md +12 -0
- package/.pbai4devs/skills/git/git-status/SKILL.md +9 -0
- package/.pbai4devs/skills/git/git-worktree/SKILL.md +10 -0
- package/.pbai4devs/skills/produto/criar-design-doc/SKILL.md +37 -0
- package/.pbai4devs/skills/produto/criar-prd/SKILL.md +35 -0
- package/.pbai4devs/skills/produto/criar-ux-ui/SKILL.md +34 -0
- package/.pbai4devs/skills/projeto/atualizar-documentacao/SKILL.md +26 -0
- package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +62 -0
- package/.pbai4devs/skills/projeto/criar-agente/SKILL.md +20 -0
- package/.pbai4devs/skills/projeto/criar-comando/SKILL.md +25 -0
- package/.pbai4devs/skills/projeto/criar-recurso/SKILL.md +41 -0
- package/.pbai4devs/skills/projeto/criar-regra/SKILL.md +19 -0
- package/.pbai4devs/skills/projeto/criar-skill/SKILL.md +22 -0
- package/.pbai4devs/skills/projeto/executar-script/SKILL.md +29 -0
- package/.pbai4devs/skills/projeto/preparar-dev/SKILL.md +45 -0
- package/.pbai4devs/skills/projeto/preparar-ferramentas/SKILL.md +56 -0
- package/.pbai4devs/skills/projeto/proximos-passos/SKILL.md +23 -0
- package/.pbai4devs/skills/projeto/sincronizar-agentes/SKILL.md +42 -0
- package/.pbai4devs/skills/projeto/usar-skills-externas/SKILL.md +28 -0
- package/.pbai4devs/skills/projeto/verificar-trabalho/SKILL.md +45 -0
- package/.pbai4devs/skills/specs/criar-spec/SKILL.md +33 -0
- package/.pbai4devs/skills/specs/gerenciar-roadmap/SKILL.md +26 -0
- package/.pbai4devs/skills/specs/implementar-spec/SKILL.md +48 -0
- package/.pbai4devs/templates/README.md +65 -0
- package/.pbai4devs/templates/adr/TEMPLATE.md +21 -0
- package/.pbai4devs/templates/ambiente-local/Makefile.template +48 -0
- package/.pbai4devs/templates/ambiente-local/TEMPLATE.md +48 -0
- package/.pbai4devs/templates/ambiente-local/compose.local.yml.template +16 -0
- package/.pbai4devs/templates/ambiente-local/dockerignore.template +28 -0
- package/.pbai4devs/templates/changelog/TEMPLATE.md +17 -0
- package/.pbai4devs/templates/claude/TEMPLATE.md +8 -0
- package/.pbai4devs/templates/clients/ADAPTERS.json +306 -0
- package/.pbai4devs/templates/clients/TEMPLATE.md +12 -0
- package/.pbai4devs/templates/comando/TEMPLATE.md +15 -0
- package/.pbai4devs/templates/configuracao/TEMPLATE.md +46 -0
- package/.pbai4devs/templates/configuracao-raiz/TEMPLATE.md +22 -0
- package/.pbai4devs/templates/configuracao-raiz/editorconfig.template +21 -0
- package/.pbai4devs/templates/configuracao-raiz/env.example.template +3 -0
- package/.pbai4devs/templates/configuracao-raiz/gitattributes.template +6 -0
- package/.pbai4devs/templates/contribuicao/TEMPLATE.md +23 -0
- package/.pbai4devs/templates/design/TEMPLATE.md +30 -0
- package/.pbai4devs/templates/entrypoints/AGENTS.md +19 -0
- package/.pbai4devs/templates/entrypoints/CLAUDE.md +7 -0
- package/.pbai4devs/templates/entrypoints/GEMINI.md +7 -0
- package/.pbai4devs/templates/entrypoints/TEMPLATE.md +12 -0
- package/.pbai4devs/templates/gitignore/TEMPLATE.md +42 -0
- package/.pbai4devs/templates/gitignore/agents.ignore +45 -0
- package/.pbai4devs/templates/gitignore/clients.json +20 -0
- package/.pbai4devs/templates/gitignore/local.ignore +10 -0
- package/.pbai4devs/templates/gitignore/shared.ignore +60 -0
- package/.pbai4devs/templates/instrucoes-agentes/TEMPLATE.md +25 -0
- package/.pbai4devs/templates/plano-implementacao/TEMPLATE.md +27 -0
- package/.pbai4devs/templates/prd/TEMPLATE.md +30 -0
- package/.pbai4devs/templates/readme-pasta/TEMPLATE.md +16 -0
- package/.pbai4devs/templates/readme-projeto/TEMPLATE.md +34 -0
- package/.pbai4devs/templates/release/TEMPLATE.md +23 -0
- package/.pbai4devs/templates/revisao-entrega/TEMPLATE.md +41 -0
- package/.pbai4devs/templates/roadmap/TEMPLATE.md +18 -0
- package/.pbai4devs/templates/seguranca/TEMPLATE.md +22 -0
- package/.pbai4devs/templates/spec/TEMPLATE.md +61 -0
- package/.pbai4devs/templates/tasks/TEMPLATE.md +20 -0
- package/.pbai4devs/templates/ux-ui/TEMPLATE.md +26 -0
- package/.pbai4devs/tools/README.md +87 -0
- package/.pbai4devs/tools/agent-browser/README.md +26 -0
- package/.pbai4devs/tools/agent-browser/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/agent-browser/TOOL.json +33 -0
- package/.pbai4devs/tools/agent-desktop/README.md +26 -0
- package/.pbai4devs/tools/agent-desktop/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/agent-desktop/TOOL.json +30 -0
- package/.pbai4devs/tools/just-bash/README.md +23 -0
- package/.pbai4devs/tools/just-bash/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/just-bash/TOOL.json +27 -0
- package/.pbai4devs/tools/markdownlint-cli2/README.md +18 -0
- package/.pbai4devs/tools/markdownlint-cli2/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/markdownlint-cli2/TOOL.json +28 -0
- package/.pbai4devs/tools/opensrc/README.md +25 -0
- package/.pbai4devs/tools/opensrc/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/opensrc/TOOL.json +29 -0
- package/.pbai4devs/tools/portless/README.md +21 -0
- package/.pbai4devs/tools/portless/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/portless/TOOL.json +30 -0
- package/.pbai4devs/tools/skills/README.md +24 -0
- package/.pbai4devs/tools/skills/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/skills/TOOL.json +33 -0
- package/.pbai4devs/tools/webreel/README.md +23 -0
- package/.pbai4devs/tools/webreel/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/webreel/TOOL.json +25 -0
- package/CHANGELOG.md +65 -0
- package/CONTRIBUTING.md +73 -0
- package/LICENSE +21 -0
- package/README.en.md +89 -0
- package/README.es.md +91 -0
- package/README.md +94 -2
- package/bin/README.md +8 -0
- package/bin/agentdevkit.mjs +35 -0
- package/docs/README.md +81 -0
- package/docs/guides/README.md +14 -0
- package/docs/guides/cli.md +81 -0
- package/docs/guides/installation.md +86 -0
- package/docs/guides/resources.md +121 -0
- package/docs/guides/tools-and-testing.md +85 -0
- package/docs/guides/troubleshooting.md +151 -0
- package/docs/guides/workflow.md +130 -0
- package/docs/maintainers/README.md +12 -0
- package/docs/maintainers/community.md +49 -0
- package/docs/maintainers/development.md +54 -0
- package/docs/maintainers/maintenance.md +104 -0
- package/docs/maintainers/packages.md +87 -0
- package/docs/product/README.md +12 -0
- package/docs/product/design.md +104 -0
- package/docs/product/prd.md +70 -0
- package/docs/product/roadmap.md +38 -0
- package/docs/product/ux-ui.md +42 -0
- package/docs/specs/README.md +21 -0
- package/docs/specs/github-packages/SPEC.md +45 -0
- package/docs/specs/v0.0.1/README.md +10 -0
- package/docs/specs/v0.0.1/SPEC.md +57 -0
- package/docs/specs/v0.0.2/README.md +10 -0
- package/docs/specs/v0.0.2/SPEC.md +67 -0
- package/docs/specs/v0.0.3/README.md +10 -0
- package/docs/specs/v0.0.3/SPEC.md +64 -0
- package/docs/specs/v0.0.4/README.md +10 -0
- package/docs/specs/v0.0.4/SPEC.md +53 -0
- package/docs/specs/v0.0.5/README.md +9 -0
- package/docs/specs/v0.0.5/SPEC.md +42 -0
- package/docs/specs/v0.0.6/README.md +10 -0
- package/docs/specs/v0.0.6/SPEC.md +77 -0
- package/package.json +67 -4
- package/src/README.md +16 -0
- package/src/adapters/index.mjs +1 -0
- package/src/cli.mjs +162 -0
- package/src/config.mjs +14 -0
- package/src/i18n.mjs +42 -0
- package/src/install.mjs +373 -0
- package/src/next.mjs +79 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Planeja em memória; somente os caminhos gerenciados pelo manifesto são alterados.
|
|
3
|
+
import { cpSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, posix, resolve } from 'node:path';
|
|
5
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
6
|
+
import { requireNode, projectRoot, isMain } from '../lib/runtime.mjs';
|
|
7
|
+
import { buildCatalog } from '../lib/catalog.mjs';
|
|
8
|
+
import { resourcePath } from '../lib/resource-path.mjs';
|
|
9
|
+
import { adapterManifest, resolveAdapters } from '../lib/adapters.mjs';
|
|
10
|
+
import { mergeGitignore, validateIgnoreClients, renderClientIgnore, isGeneratedOutput, planLocalIgnore, applyLocalIgnore, checkLocalIgnore, ignoreBegin, ignoreEnd } from '../lib/gitignore.mjs';
|
|
11
|
+
|
|
12
|
+
const marker = '<!-- gerado: sincronizar-agentes -->';
|
|
13
|
+
const tomlMarker = '# gerado: sincronizar-agentes';
|
|
14
|
+
const principals = { skills: 'SKILL.md', agents: 'AGENT.md', commands: 'COMMAND.md', rules: 'RULE.md' };
|
|
15
|
+
const auxiliary = ['templates', 'scripts', 'tools', 'external-skills', 'estado', 'tests', 'docs', 'adapters'];
|
|
16
|
+
const reserved = new Set(['.git', '.pbai4devs', '.agent-tests']);
|
|
17
|
+
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
18
|
+
const sorted = values => [...new Set(values)].sort(compare);
|
|
19
|
+
const layouts = new Set(['agrupado', 'plano', 'arquivo', 'mdc', 'instrucoes', 'prompt', 'agente']);
|
|
20
|
+
const fail = message => { throw new Error(message); };
|
|
21
|
+
const normalize = text => text.replace(/\r\n?/g, '\n');
|
|
22
|
+
const read = file => normalize(readFileSync(file, 'utf8'));
|
|
23
|
+
const stat = file => {
|
|
24
|
+
try { return lstatSync(file); }
|
|
25
|
+
catch (error) { if (['ENOENT', 'ENOTDIR'].includes(error.code)) return null; throw error; }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function header(text) {
|
|
29
|
+
const lines = normalize(text).split('\n');
|
|
30
|
+
if (lines[0] !== '---') fail('Frontmatter YAML ausente.');
|
|
31
|
+
const end = lines.indexOf('---', 1);
|
|
32
|
+
if (end < 0) fail('Frontmatter YAML sem fechamento.');
|
|
33
|
+
return lines.slice(0, end + 1).join('\n') + '\n';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isReference(text) {
|
|
37
|
+
let body = normalize(text);
|
|
38
|
+
if (body.startsWith(tomlMarker + '\n')) {
|
|
39
|
+
const lines = body.trimEnd().split('\n');
|
|
40
|
+
if (lines.length !== 3 || !lines[1].startsWith('description = ') || !lines[2].startsWith('prompt = ')) return false;
|
|
41
|
+
try {
|
|
42
|
+
const description = JSON.parse(lines[1].slice('description = '.length));
|
|
43
|
+
const prompt = JSON.parse(lines[2].slice('prompt = '.length));
|
|
44
|
+
const match = typeof prompt === 'string' && prompt.match(/^@\{(\.pbai4devs\/commands\/[^{}\r\n]+\/COMMAND\.md)\}\n\n\{\{args\}\}$/);
|
|
45
|
+
return typeof description === 'string' && Boolean(description.trim()) && Boolean(match) && !match[1].split('/').some(part => part === '..' || part === '.');
|
|
46
|
+
} catch { return false; }
|
|
47
|
+
}
|
|
48
|
+
if (body.startsWith('---\n')) {
|
|
49
|
+
try { body = body.slice(header(body).length); } catch { return false; }
|
|
50
|
+
}
|
|
51
|
+
let title = false;
|
|
52
|
+
const action = [];
|
|
53
|
+
for (const line of body.split('\n')) {
|
|
54
|
+
if (!line.trim() || line === marker) continue;
|
|
55
|
+
if (!title && line.startsWith('# ')) { title = true; continue; }
|
|
56
|
+
action.push(line);
|
|
57
|
+
}
|
|
58
|
+
const instruction = action.join(' ').replace(/\s+/g, ' ').replace(/\) \.$/, ').');
|
|
59
|
+
return title && (/^(Leia e siga|Leia) \[[^\]]+\]\([^)]*\)( ao realizar operações Git)?\.$/.test(instruction) || /^@\[[^\]\r\n]+\](?:\([^)]*\))?$/.test(instruction));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function topPaths(paths) {
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const path of sorted(paths)) if (!result.some(parent => path.startsWith(parent + '/'))) result.push(path);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function link(target, source) {
|
|
69
|
+
return posix.relative(posix.dirname(target), source).split('/').map(part => encodeURIComponent(part).replace(/[!'()*]/g, character => '%' + character.charCodeAt(0).toString(16).toUpperCase())).join('/');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { resourcePath } from '../lib/resource-path.mjs';
|
|
73
|
+
|
|
74
|
+
export function synchronize({ root = projectRoot, check = false, force = false, localIgnores = true, generateEntrypoints = true, output = console.log } = {}) {
|
|
75
|
+
requireNode();
|
|
76
|
+
root = realpathSync(resolve(root));
|
|
77
|
+
const absolute = value => join(root, ...value.split('/'));
|
|
78
|
+
function safe(value) {
|
|
79
|
+
if (typeof value !== 'string' || !value || /^[A-Za-z]:/.test(value) || /[|\\\x00-\x1f\x7f]/.test(value) || value.split('/').some(part => !part || part === '.' || part === '..')) fail(`Caminho invalido: ${value}`);
|
|
80
|
+
let current = root;
|
|
81
|
+
for (const part of value.split('/')) {
|
|
82
|
+
const parent = stat(current);
|
|
83
|
+
if (parent?.isDirectory()) {
|
|
84
|
+
const aliases = readdirSync(current).filter(name => name.toLowerCase() === part.toLowerCase());
|
|
85
|
+
if (aliases.some(name => name !== part)) fail(`Caminhos diferem somente por maiusculas/minusculas: ${value}`);
|
|
86
|
+
}
|
|
87
|
+
current = join(current, part);
|
|
88
|
+
if (stat(current)?.isSymbolicLink()) fail(`Link simbolico: ${value}`);
|
|
89
|
+
}
|
|
90
|
+
return current;
|
|
91
|
+
}
|
|
92
|
+
function scan(value, { markdownOnly = false, prune = false } = {}) {
|
|
93
|
+
const path = safe(value);
|
|
94
|
+
const item = stat(path);
|
|
95
|
+
if (!item) return [];
|
|
96
|
+
if (item.isFile()) return !markdownOnly || value.endsWith('.md') ? [value] : [];
|
|
97
|
+
if (!item.isDirectory()) fail(`Tipo de arquivo nao suportado: ${value}`);
|
|
98
|
+
const files = [];
|
|
99
|
+
function visit(folder) {
|
|
100
|
+
for (const name of readdirSync(safe(folder)).sort(compare)) {
|
|
101
|
+
const child = `${folder}/${name}`;
|
|
102
|
+
const info = stat(safe(child));
|
|
103
|
+
if (prune && isGeneratedOutput(child, info.isDirectory())) continue;
|
|
104
|
+
if (info.isDirectory()) {
|
|
105
|
+
files.push(...scan(child, { markdownOnly, prune }));
|
|
106
|
+
} else if (info.isFile()) {
|
|
107
|
+
if (!markdownOnly || name.endsWith('.md')) files.push(child);
|
|
108
|
+
} else fail(`Tipo de arquivo nao suportado: ${child}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
visit(value);
|
|
112
|
+
return files;
|
|
113
|
+
}
|
|
114
|
+
const referenceFile = value => {
|
|
115
|
+
const info = stat(safe(value));
|
|
116
|
+
return Boolean(info?.isFile() && info.size <= 1024 * 1024 && isReference(read(absolute(value))));
|
|
117
|
+
};
|
|
118
|
+
const onlyReferences = value => scan(value).every(file => /\.(md|mdc|toml)$/.test(file) && referenceFile(file));
|
|
119
|
+
const protectedPath = value => /^(workflows(?:\/|$)|settings\.json$|extensions\.json$)/.test(value.slice(value.indexOf('/') + 1).toLowerCase());
|
|
120
|
+
const agents = new Map();
|
|
121
|
+
const formats = new Map();
|
|
122
|
+
const discoveries = new Set();
|
|
123
|
+
const ignoreRoots = new Set();
|
|
124
|
+
const natives = [];
|
|
125
|
+
const legacy = [];
|
|
126
|
+
const config = '.pbai4devs/espelhos.conf';
|
|
127
|
+
const configExists = stat(safe(config));
|
|
128
|
+
const stateFile = '.pbai4devs/estado/agentdevkit.json';
|
|
129
|
+
const installed = stat(safe(stateFile)) ? JSON.parse(read(safe(stateFile))) : null;
|
|
130
|
+
const configText = configExists ? read(safe(config)) : adapterManifest(root, { clients: installed?.clients });
|
|
131
|
+
const rows = configText.split('\n');
|
|
132
|
+
function ignoreRoot(target) {
|
|
133
|
+
if (!/^\.[^/]+$/.test(target) || reserved.has(target.toLowerCase())) fail(`Pasta de agente invalida: ${target}`);
|
|
134
|
+
const info = stat(absolute(target));
|
|
135
|
+
if (info && !info.isDirectory()) fail(`Pasta de agente nao e diretorio: ${target}`);
|
|
136
|
+
for (const known of ignoreRoots) if (known !== target && known.toLowerCase() === target.toLowerCase()) fail('Raizes de agentes diferem somente por maiusculas/minusculas.');
|
|
137
|
+
ignoreRoots.add(target);
|
|
138
|
+
}
|
|
139
|
+
for (const [index, row] of rows.entries()) {
|
|
140
|
+
if (!row || row.startsWith('#')) continue;
|
|
141
|
+
const fields = row.split('|');
|
|
142
|
+
if (fields.length < 2 || fields.length > 4) fail('Campos invalidos no manifesto.');
|
|
143
|
+
const [type, target, source = '', metadata = ''] = fields;
|
|
144
|
+
safe(target);
|
|
145
|
+
if (type === 'agente') {
|
|
146
|
+
if (fields.length > 3) fail(`Pasta de agente invalida: ${target}`);
|
|
147
|
+
ignoreRoot(target);
|
|
148
|
+
const mode = source || 'agrupado';
|
|
149
|
+
if (!['agrupado', 'plano'].includes(mode)) fail(`Layout invalido: ${mode}`);
|
|
150
|
+
for (const known of agents.keys()) if (known !== target && known.toLowerCase() === target.toLowerCase()) fail('Raizes de agentes diferem somente por maiusculas/minusculas.');
|
|
151
|
+
if (agents.has(target) && agents.get(target) !== mode) fail(`Layouts conflitantes: ${target}`);
|
|
152
|
+
agents.set(target, mode);
|
|
153
|
+
} else if (type === 'formato') {
|
|
154
|
+
if (fields.length !== 4 || !Object.hasOwn(principals, source) || !layouts.has(metadata)) fail(`Formato invalido: ${row}`);
|
|
155
|
+
if (source === 'skills' && !['agrupado', 'plano'].includes(metadata)) fail(`Skills exigem formato agrupado ou plano: ${row}`);
|
|
156
|
+
if ((metadata === 'mdc' || metadata === 'instrucoes') && source !== 'rules' || metadata === 'prompt' && source !== 'commands' || metadata === 'agente' && source !== 'agents') fail(`Formato incompativel com recurso: ${row}`);
|
|
157
|
+
const key = `${target}/${source}`;
|
|
158
|
+
if (formats.has(key)) fail(`Formato duplicado: ${key}`);
|
|
159
|
+
formats.set(key, metadata);
|
|
160
|
+
} else if (type === 'descoberta') {
|
|
161
|
+
if (fields.length !== 3 || target !== '.agents' || source !== 'antigravity' || discoveries.has(target)) fail(`Descoberta invalida: ${row}`);
|
|
162
|
+
discoveries.add(target);
|
|
163
|
+
} else if (type === 'gitignore') {
|
|
164
|
+
if (fields.length !== 2) fail('gitignore aceita somente a pasta de destino.');
|
|
165
|
+
ignoreRoot(target);
|
|
166
|
+
} else if (type === 'documento') legacy.push(index);
|
|
167
|
+
else if (type === 'nativo') {
|
|
168
|
+
safe(source);
|
|
169
|
+
if (metadata) {
|
|
170
|
+
safe(metadata);
|
|
171
|
+
if (posix.dirname(metadata) !== posix.dirname(source) || !/\.ya?ml$/.test(metadata)) fail(`Metadados fora da pasta da origem ou sem YAML: ${metadata}`);
|
|
172
|
+
if (stat(absolute(source))?.isFile()) {
|
|
173
|
+
if (!stat(absolute(metadata))?.isFile()) fail(`Metadados ausentes: ${metadata}`);
|
|
174
|
+
const text = read(absolute(metadata));
|
|
175
|
+
if (text.slice(header(text).length).trim()) fail(`Metadados devem conter apenas YAML: ${metadata}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
natives.push({ target, source, metadata, index });
|
|
179
|
+
} else fail(`Tipo invalido no manifesto: ${type}`);
|
|
180
|
+
}
|
|
181
|
+
if (!agents.size) fail('Nenhum agente cadastrado.');
|
|
182
|
+
for (const key of formats.keys()) if (!agents.has(key.split('/')[0])) fail(`Formato fora dos agentes: ${key}`);
|
|
183
|
+
for (const root of discoveries) if (!agents.has(root)) fail(`Descoberta fora dos agentes: ${root}`);
|
|
184
|
+
const managed = value => [...agents.keys()].some(agent => value.startsWith(agent + '/'));
|
|
185
|
+
const built = buildCatalog(root);
|
|
186
|
+
const sources = new Map(Object.keys(principals).flatMap(kind => built.catalog[kind].map(entry => [entry.path, kind])));
|
|
187
|
+
const entries = new Map();
|
|
188
|
+
const folded = new Map();
|
|
189
|
+
function add(target, source, metadata = '') {
|
|
190
|
+
safe(target);
|
|
191
|
+
const alias = folded.get(target.toLowerCase());
|
|
192
|
+
if (alias && alias !== target) fail(`Destinos diferem somente por maiusculas/minusculas: ${target}`);
|
|
193
|
+
const previous = entries.get(target);
|
|
194
|
+
if (previous && (previous.source !== source || (previous.metadata && metadata && previous.metadata !== metadata))) fail(`Destinos ou metadados em conflito: ${target}`);
|
|
195
|
+
entries.set(target, { source, metadata: metadata || previous?.metadata || '' });
|
|
196
|
+
folded.set(target.toLowerCase(), target);
|
|
197
|
+
}
|
|
198
|
+
for (const [source, kind] of sources) {
|
|
199
|
+
for (const [agent, mode] of agents) {
|
|
200
|
+
let path = resourcePath(source, kind, formats.get(`${agent}/${kind}`) || mode);
|
|
201
|
+
if (agent === '.gemini' && kind === 'commands') path = path.replace(/\.md$/, '.toml');
|
|
202
|
+
add(`${agent}/${path}`, source);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const missing = [];
|
|
206
|
+
for (const native of natives) {
|
|
207
|
+
const { target, source, metadata } = native;
|
|
208
|
+
if (!Object.entries(principals).some(([kind, name]) => source.startsWith(`.pbai4devs/${kind}/`) && source.slice(`.pbai4devs/${kind}/`.length).includes('/') && posix.basename(source) === name)) fail(`Origem exclusiva de .pbai4devs ou documento nao permitido: ${source}`);
|
|
209
|
+
if (stat(absolute(source))?.isFile() && !sources.has(source)) fail(`Origem nativa fora do catalogo canonico: ${source}`);
|
|
210
|
+
if (!managed(target)) fail(`Destino nativo fora dos agentes: ${target}`);
|
|
211
|
+
const scope = target.split('/')[1].toLowerCase();
|
|
212
|
+
const geminiCommand = target.startsWith('.gemini/commands/') && target.endsWith('.toml') && source.startsWith('.pbai4devs/commands/') && posix.basename(source) === 'COMMAND.md';
|
|
213
|
+
if (target.startsWith('.gemini/commands/') && !geminiCommand) fail(`Comando Gemini exige TOML e origem COMMAND.md: ${target}`);
|
|
214
|
+
if ([...auxiliary, 'workflows'].includes(scope) || ['readme.md', 'instructions.md'].includes(posix.basename(target).toLowerCase()) || (!/\.(md|mdc)$/i.test(target) && !geminiCommand)) fail(`Destino nativo nao permitido: ${target}`);
|
|
215
|
+
if (geminiCommand && metadata) fail(`Comando Gemini usa metadados da origem, sem YAML separado: ${target}`);
|
|
216
|
+
if (stat(absolute(source))?.isFile()) add(target, source, metadata);
|
|
217
|
+
else missing.push(native);
|
|
218
|
+
}
|
|
219
|
+
const targets = sorted(entries.keys());
|
|
220
|
+
for (const target of targets) {
|
|
221
|
+
const parts = target.split('/');
|
|
222
|
+
while (parts.length > 1) {
|
|
223
|
+
parts.pop();
|
|
224
|
+
if (folded.has(parts.join('/').toLowerCase())) fail(`Destino e ancestral de outro destino: ${target}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const remove = new Set();
|
|
228
|
+
const backup = new Set();
|
|
229
|
+
const conflicts = new Set();
|
|
230
|
+
const expected = new Map();
|
|
231
|
+
const changed = [];
|
|
232
|
+
function planRemove(value, custom = false) {
|
|
233
|
+
safe(value);
|
|
234
|
+
if (!managed(value) || protectedPath(value)) fail(`Limpeza fora dos espelhos ou caminho protegido: ${value}`);
|
|
235
|
+
remove.add(value);
|
|
236
|
+
if (custom) backup.add(value);
|
|
237
|
+
}
|
|
238
|
+
function cleanScope(value) {
|
|
239
|
+
if (!stat(safe(value))) return;
|
|
240
|
+
if (force && value.split('/')[1] === 'external-skills') planRemove(value, true);
|
|
241
|
+
else if (onlyReferences(value)) planRemove(value);
|
|
242
|
+
else if (force) planRemove(value, true);
|
|
243
|
+
else conflicts.add(value);
|
|
244
|
+
}
|
|
245
|
+
const registries = new Map();
|
|
246
|
+
for (const [target, content] of built.outputs) {
|
|
247
|
+
const info = stat(safe(target));
|
|
248
|
+
if (info && !info.isFile()) fail(`Registro nao e arquivo: ${target}`);
|
|
249
|
+
if (!info || readFileSync(absolute(target), 'utf8') !== content) {
|
|
250
|
+
registries.set(target, content);
|
|
251
|
+
if (force && info) backup.add(target);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const agent of discoveries) {
|
|
255
|
+
for (const kind of ['skills', 'rules', 'agents']) {
|
|
256
|
+
const manifest = `${agent}/${kind}.json`;
|
|
257
|
+
const paths = [...entries].filter(([target, value]) => target.startsWith(`${agent}/`) && sources.get(value.source) === kind)
|
|
258
|
+
.map(([target]) => kind === 'skills' ? posix.dirname(target) : target).sort(compare);
|
|
259
|
+
const content = JSON.stringify({ entries: paths.map(path => ({ path })) }, null, 2) + '\n';
|
|
260
|
+
const info = stat(safe(manifest));
|
|
261
|
+
if (info && !info.isFile()) fail(`Registro nao e arquivo: ${manifest}`);
|
|
262
|
+
if (!info || readFileSync(absolute(manifest), 'utf8') !== content) {
|
|
263
|
+
let managedManifest = false;
|
|
264
|
+
if (info) {
|
|
265
|
+
try {
|
|
266
|
+
const previous = JSON.parse(read(absolute(manifest)));
|
|
267
|
+
managedManifest = Object.keys(previous).length === 1 && Array.isArray(previous.entries) && previous.entries.every(entry =>
|
|
268
|
+
entry && Object.keys(entry).length === 1 && typeof entry.path === 'string' && entry.path.startsWith(`${agent}/${kind}/`) && !entry.path.split('/').some(part => !part || part === '..' || part === '.')
|
|
269
|
+
);
|
|
270
|
+
} catch { /* Um manifesto customizado ou corrompido requer --force e backup. */ }
|
|
271
|
+
}
|
|
272
|
+
if (info && !managedManifest && !force) conflicts.add(manifest);
|
|
273
|
+
if (info && force) backup.add(manifest);
|
|
274
|
+
registries.set(manifest, content);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const ignoreFiles = new Map();
|
|
279
|
+
const clientSettingsPath = '.pbai4devs/templates/gitignore/clients.json';
|
|
280
|
+
if (!stat(safe(clientSettingsPath))?.isFile()) fail(`Configuracao .gitignore ausente: ${clientSettingsPath}`);
|
|
281
|
+
const ignoreClients = validateIgnoreClients(JSON.parse(read(absolute(clientSettingsPath))));
|
|
282
|
+
const ignoreTemplates = new Map([
|
|
283
|
+
['.pbai4devs/.gitignore', '.pbai4devs/templates/gitignore/shared.ignore'],
|
|
284
|
+
...sorted(ignoreRoots).map(folder => [`${folder}/.gitignore`, '.pbai4devs/templates/gitignore/agents.ignore']),
|
|
285
|
+
]);
|
|
286
|
+
for (const [target, template] of ignoreTemplates) {
|
|
287
|
+
if (!stat(safe(template))?.isFile()) fail(`Template .gitignore ausente ou invalido: ${template}`);
|
|
288
|
+
const info = stat(safe(target));
|
|
289
|
+
if (info && !info.isFile()) fail(`.gitignore nao e arquivo: ${target}`);
|
|
290
|
+
let merged;
|
|
291
|
+
const current = info ? readFileSync(absolute(target), 'utf8') : '';
|
|
292
|
+
try {
|
|
293
|
+
let patterns = readFileSync(absolute(template), 'utf8');
|
|
294
|
+
if (target !== '.pbai4devs/.gitignore') patterns = renderClientIgnore(patterns, ignoreClients[posix.dirname(target)]);
|
|
295
|
+
merged = mergeGitignore(current, patterns);
|
|
296
|
+
}
|
|
297
|
+
catch (error) { fail(`${target}: ${error.message}`); }
|
|
298
|
+
if (merged.text !== current) {
|
|
299
|
+
ignoreFiles.set(target, merged.text);
|
|
300
|
+
if (force && info && merged.hasBlock) backup.add(target);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const target of targets) {
|
|
304
|
+
const { source, metadata } = entries.get(target);
|
|
305
|
+
let ancestor = posix.dirname(target);
|
|
306
|
+
while (ancestor.includes('/')) {
|
|
307
|
+
const info = stat(safe(ancestor));
|
|
308
|
+
if (info && !info.isDirectory()) {
|
|
309
|
+
if (force) planRemove(ancestor, true); else conflicts.add(ancestor);
|
|
310
|
+
}
|
|
311
|
+
ancestor = posix.dirname(ancestor);
|
|
312
|
+
}
|
|
313
|
+
const info = stat(safe(target));
|
|
314
|
+
if (info && (!info.isFile() || !referenceFile(target))) {
|
|
315
|
+
scan(target);
|
|
316
|
+
if (force) planRemove(target, true); else conflicts.add(target);
|
|
317
|
+
}
|
|
318
|
+
const sourceText = read(safe(source));
|
|
319
|
+
if (target.startsWith('.gemini/commands/') && target.endsWith('.toml')) {
|
|
320
|
+
if (/[{}]/.test(source)) fail(`Caminho incompativel com inclusao Gemini: ${source}`);
|
|
321
|
+
const command = built.catalog.commands.find(entry => entry.path === source);
|
|
322
|
+
if (!command) fail(`Comando Gemini exige COMMAND.md: ${source}`);
|
|
323
|
+
const content = `${tomlMarker}\ndescription = ${JSON.stringify(command.description)}\nprompt = ${JSON.stringify(`@{${source}}\n\n{{args}}`)}\n`;
|
|
324
|
+
expected.set(target, content);
|
|
325
|
+
if (!info?.isFile() || readFileSync(absolute(target), 'utf8') !== content) changed.push(target);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
let frontmatter;
|
|
329
|
+
if (metadata) frontmatter = header(read(safe(metadata)));
|
|
330
|
+
else if (target.endsWith('.mdc')) {
|
|
331
|
+
const rule = built.catalog.rules.find(entry => entry.path === source);
|
|
332
|
+
if (!rule) fail(`Regra Cursor exige RULE.md: ${source}`);
|
|
333
|
+
frontmatter = `---\ndescription: ${JSON.stringify(rule.trigger === 'manual' ? '' : rule.description)}\nalwaysApply: ${rule.trigger === 'always_on'}\n${rule.trigger === 'glob' ? `globs: ${JSON.stringify(rule.globs)}\n` : ''}---\n`;
|
|
334
|
+
} else frontmatter = target.endsWith('.instructions.md') ? "---\napplyTo: '**'\n---\n" : header(sourceText);
|
|
335
|
+
const title = sourceText.match(/^# .+$/m)?.[0];
|
|
336
|
+
if (!title) fail(`Titulo ausente: ${source}`);
|
|
337
|
+
const ruleInclusion = /^\.(?:agents|agent)\/rules\//.test(target);
|
|
338
|
+
const inclusion = ruleInclusion ? `@[origem](${link(target, source)})` : `Leia e siga [origem](${link(target, source)}).`;
|
|
339
|
+
const content = `${frontmatter}\n${title}\n\n${marker}\n${inclusion}\n`;
|
|
340
|
+
expected.set(target, content);
|
|
341
|
+
if (!info?.isFile() || readFileSync(absolute(target), 'utf8') !== content) changed.push(target);
|
|
342
|
+
}
|
|
343
|
+
for (const agent of agents.keys()) {
|
|
344
|
+
for (const kind of [...auxiliary, 'README.md', 'INSTRUCTIONS.md']) cleanScope(`${agent}/${kind}`);
|
|
345
|
+
for (const kind of Object.keys(principals)) {
|
|
346
|
+
const scope = `${agent}/${kind}`;
|
|
347
|
+
const info = stat(safe(scope));
|
|
348
|
+
if (!info) continue;
|
|
349
|
+
const files = scan(scope);
|
|
350
|
+
if (!info.isDirectory()) { if (force) planRemove(scope, true); else conflicts.add(scope); continue; }
|
|
351
|
+
for (const file of files) if (!expected.has(file)) {
|
|
352
|
+
if (referenceFile(file)) planRemove(file);
|
|
353
|
+
else if (force) planRemove(file, true);
|
|
354
|
+
else conflicts.add(file);
|
|
355
|
+
}
|
|
356
|
+
function directories(value) {
|
|
357
|
+
for (const name of readdirSync(safe(value))) {
|
|
358
|
+
const child = `${value}/${name}`;
|
|
359
|
+
if (!stat(safe(child))?.isDirectory()) continue;
|
|
360
|
+
if (![...expected.keys()].some(target => target.startsWith(child + '/'))) cleanScope(child);
|
|
361
|
+
directories(child);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
directories(scope);
|
|
365
|
+
}
|
|
366
|
+
for (const file of scan(agent, { markdownOnly: true, prune: true })) {
|
|
367
|
+
if (protectedPath(file) || expected.has(file) || !read(absolute(file)).includes(marker)) continue;
|
|
368
|
+
if (referenceFile(file)) planRemove(file);
|
|
369
|
+
else if (force) planRemove(file, true);
|
|
370
|
+
else conflicts.add(file);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const { target } of missing) cleanScope(target);
|
|
374
|
+
const rootEntries = new Map();
|
|
375
|
+
const entryRegistry = '.pbai4devs/estado/derived.json';
|
|
376
|
+
if (generateEntrypoints) {
|
|
377
|
+
const previous = stat(safe(entryRegistry)) ? JSON.parse(read(safe(entryRegistry))) : { schemaVersion: 1, entries: {} };
|
|
378
|
+
if (previous?.schemaVersion !== 1 || !previous.entries || typeof previous.entries !== 'object' || Array.isArray(previous.entries) || Object.entries(previous.entries).some(([name, hash]) => !['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'].includes(name) || !/^[a-f0-9]{64}$/.test(hash))) fail('Registro de entradas derivadas invalido.');
|
|
379
|
+
const next = { schemaVersion: 1, entries: {} };
|
|
380
|
+
for (const name of resolveAdapters(installed?.clients).entrypoints) {
|
|
381
|
+
const template = `.pbai4devs/templates/entrypoints/${name}`;
|
|
382
|
+
if (!stat(safe(template))?.isFile()) continue;
|
|
383
|
+
const info = stat(safe(name));
|
|
384
|
+
const current = info?.isFile() ? readFileSync(absolute(name)) : null;
|
|
385
|
+
const previousHash = previous.entries[name] ?? installed?.files?.[name];
|
|
386
|
+
if (info && (!current || createHash('sha256').update(current).digest('hex') !== previousHash)) continue;
|
|
387
|
+
if (current && createHash('sha256').update(current).digest('hex') === installed?.files?.[name]) {
|
|
388
|
+
rootEntries.set(name, current.toString('utf8'));
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const content = readFileSync(absolute(template), 'utf8');
|
|
392
|
+
const nextHash = createHash('sha256').update(content).digest('hex');
|
|
393
|
+
next.entries[name] = nextHash;
|
|
394
|
+
rootEntries.set(name, content);
|
|
395
|
+
}
|
|
396
|
+
const content = JSON.stringify(next, null, 2) + '\n';
|
|
397
|
+
if ((Object.keys(next.entries).length || stat(safe(entryRegistry))) && (!stat(safe(entryRegistry)) || readFileSync(absolute(entryRegistry), 'utf8') !== content)) registries.set(entryRegistry, content);
|
|
398
|
+
}
|
|
399
|
+
const ownedOutputs = sorted([...targets, ...built.outputs.keys(), '.pbai4devs/espelhos.conf', ...rootEntries.keys(), ...(generateEntrypoints && (stat(safe(entryRegistry)) || registries.has(entryRegistry)) ? [entryRegistry] : []),
|
|
400
|
+
...[...discoveries].flatMap(agent => ['skills', 'rules', 'agents'].map(kind => `${agent}/${kind}.json`)),
|
|
401
|
+
...[...ignoreTemplates.keys()].filter(target => {
|
|
402
|
+
if (target === '.pbai4devs/.gitignore') return false;
|
|
403
|
+
const content = ignoreFiles.get(target) ?? readFileSync(absolute(target), 'utf8');
|
|
404
|
+
const start = content.indexOf(ignoreBegin), finish = content.indexOf(ignoreEnd);
|
|
405
|
+
return start >= 0 && finish > start && !(content.slice(0, start) + content.slice(finish + ignoreEnd.length)).trim();
|
|
406
|
+
}),
|
|
407
|
+
]);
|
|
408
|
+
const localPlan = localIgnores ? planLocalIgnore(root, { outputs: ownedOutputs }) : null;
|
|
409
|
+
const localStatus = localPlan ? checkLocalIgnore(localPlan) : { changed: 0, missing: [], trackedOutputs: [] };
|
|
410
|
+
const removals = topPaths(remove);
|
|
411
|
+
const backups = topPaths(backup);
|
|
412
|
+
const rootEntryChanges = [...rootEntries].filter(([name, content]) => !stat(absolute(name)) || readFileSync(absolute(name), 'utf8') !== content);
|
|
413
|
+
const summary = { changed: changed.length + rootEntryChanges.length, stale: removals.length, conflicts: conflicts.size, missing: missing.length, legacy: legacy.length, registries: registries.size, gitignores: ignoreFiles.size, manifests: configExists ? 0 : 1, localIgnores: localStatus.changed + localStatus.missing.length + localStatus.trackedOutputs.length };
|
|
414
|
+
const dirty = Object.values(summary).some(Boolean);
|
|
415
|
+
output(`${summary.changed} desatualizadas; ${summary.stale} obsoletas; ${summary.conflicts} conflitos; ${summary.missing} origens nativas ausentes; ${summary.legacy} documentos antigos; ${summary.registries} indices derivados; ${summary.gitignores} gitignores; ${summary.localIgnores} exclusoes locais.`);
|
|
416
|
+
if (check) return { ...summary, ownedOutputs, exitCode: dirty ? 1 : 0 };
|
|
417
|
+
if (conflicts.size) fail(`Use --force para reconstruir somente os caminhos gerenciados: ${sorted(conflicts).join(', ')}`);
|
|
418
|
+
function atomic(value, text) {
|
|
419
|
+
const target = safe(value);
|
|
420
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
421
|
+
const temporary = `${target}.sync.${randomUUID()}`;
|
|
422
|
+
try { writeFileSync(temporary, text, { flag: 'wx' }); renameSync(temporary, target); }
|
|
423
|
+
finally { rmSync(temporary, { force: true }); }
|
|
424
|
+
}
|
|
425
|
+
let backupRoot;
|
|
426
|
+
if (backups.length) {
|
|
427
|
+
mkdirSync(safe('.agent-tests'), { recursive: true });
|
|
428
|
+
backupRoot = mkdtempSync(join(absolute('.agent-tests'), 'sync-backup.'));
|
|
429
|
+
for (const value of backups) if (stat(safe(value))) {
|
|
430
|
+
const destination = join(backupRoot, ...value.split('/'));
|
|
431
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
432
|
+
cpSync(absolute(value), destination, { recursive: true, preserveTimestamps: true });
|
|
433
|
+
}
|
|
434
|
+
output(`Backup local: ${posix.join('.agent-tests', backupRoot.slice(backupRoot.lastIndexOf('/') + 1).split('\\').at(-1))}`);
|
|
435
|
+
}
|
|
436
|
+
for (const value of removals) rmSync(safe(value), { recursive: true, force: true });
|
|
437
|
+
for (const value of changed) atomic(value, expected.get(value));
|
|
438
|
+
for (const [value, text] of registries) atomic(value, text);
|
|
439
|
+
for (const [value, text] of ignoreFiles) atomic(value, text);
|
|
440
|
+
for (const [value, text] of rootEntryChanges) atomic(value, text);
|
|
441
|
+
if (localPlan) applyLocalIgnore(localPlan);
|
|
442
|
+
function pruneEmpty(value) {
|
|
443
|
+
if (!stat(safe(value))?.isDirectory()) return;
|
|
444
|
+
for (const name of readdirSync(absolute(value))) if (stat(safe(`${value}/${name}`))?.isDirectory()) pruneEmpty(`${value}/${name}`);
|
|
445
|
+
if (!readdirSync(absolute(value)).length) rmdirSync(absolute(value));
|
|
446
|
+
}
|
|
447
|
+
for (const agent of agents.keys()) for (const kind of [...Object.keys(principals), 'instructions', 'prompts']) pruneEmpty(`${agent}/${kind}`);
|
|
448
|
+
const omittedRows = new Set([...legacy, ...missing.map(native => native.index)]);
|
|
449
|
+
const finalConfig = rows.filter((row, index) => !omittedRows.has(index)).join('\n').replace(/\n*$/, '\n');
|
|
450
|
+
if (!configExists || configText !== finalConfig) atomic(config, finalConfig);
|
|
451
|
+
return { ...summary, ownedOutputs, exitCode: 0, ...(backupRoot ? { backupRoot } : {}) };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function main(args = process.argv.slice(2)) {
|
|
455
|
+
requireNode();
|
|
456
|
+
const options = {};
|
|
457
|
+
for (let index = 0; index < args.length; index++) {
|
|
458
|
+
const option = args[index];
|
|
459
|
+
if (option === '--check') options.check = true;
|
|
460
|
+
else if (option === '--force') options.force = true;
|
|
461
|
+
else if (option === '--root') {
|
|
462
|
+
if (!args[index + 1] || args[index + 1].startsWith('--')) fail('--root exige um caminho.');
|
|
463
|
+
options.root = args[++index];
|
|
464
|
+
} else if (option === '--documento') fail('--documento foi removido: somente skills, agents, commands e rules sao sincronizados.');
|
|
465
|
+
else fail(`Opcao desconhecida: ${option}`);
|
|
466
|
+
}
|
|
467
|
+
return synchronize(options).exitCode;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (isMain(import.meta.url)) {
|
|
471
|
+
try { process.exitCode = main(); }
|
|
472
|
+
catch (error) { console.error(error.message); process.exitCode = 2; }
|
|
473
|
+
}
|