@pathbit/agentdevkit 0.0.8 → 0.0.9
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 +4 -2
- package/.pbai4devs/INSTRUCTIONS.md +4 -0
- package/.pbai4devs/commands/sincronizar/COMMAND.md +4 -3
- package/.pbai4devs/rules/documentacao/RULE.md +4 -0
- package/.pbai4devs/rules/qualidade/RULE.md +4 -2
- package/.pbai4devs/scripts/README.md +3 -3
- package/.pbai4devs/scripts/inspecionar-projeto/README.md +31 -0
- package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +142 -1
- package/.pbai4devs/scripts/inspecionar-projeto/tests/TEST.mjs +168 -1
- package/.pbai4devs/scripts/lib/native-paths.mjs +35 -0
- package/.pbai4devs/scripts/sincronizar-agentes/README.md +3 -0
- package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +73 -29
- package/.pbai4devs/scripts/sincronizar-agentes/tests/COEXISTENCE.mjs +190 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/HUMAN_OWNERSHIP.mjs +8 -2
- package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +28 -28
- package/.pbai4devs/scripts/verificar-recursos/SCRIPT.mjs +56 -8
- package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +92 -1
- package/.pbai4devs/skills/produto/criar-design-doc/SKILL.md +2 -0
- package/.pbai4devs/skills/produto/criar-prd/SKILL.md +2 -0
- package/.pbai4devs/skills/produto/criar-ux-ui/SKILL.md +2 -0
- package/.pbai4devs/skills/projeto/atualizar-documentacao/SKILL.md +18 -4
- package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +2 -0
- package/.pbai4devs/skills/projeto/criar-recurso/SKILL.md +1 -0
- package/.pbai4devs/skills/projeto/sincronizar-agentes/SKILL.md +3 -2
- package/.pbai4devs/skills/specs/criar-spec/SKILL.md +1 -0
- package/.pbai4devs/skills/specs/gerenciar-roadmap/SKILL.md +2 -1
- package/.pbai4devs/templates/README.md +4 -1
- package/.pbai4devs/templates/configuracao/TEMPLATE.md +6 -0
- package/CHANGELOG.md +10 -0
- package/README.en.md +3 -2
- package/README.es.md +3 -2
- package/README.md +3 -2
- package/docs/README.md +1 -0
- package/docs/guides/README.md +1 -0
- package/docs/guides/cli.md +2 -0
- package/docs/guides/documentation.md +63 -0
- package/docs/guides/installation.md +12 -5
- package/docs/guides/resources.md +6 -5
- package/docs/guides/troubleshooting.md +6 -6
- package/docs/guides/workflow.md +2 -0
- package/docs/maintainers/packages.md +1 -1
- package/docs/product/design.md +12 -0
- package/docs/product/prd.md +4 -1
- package/docs/product/roadmap.md +2 -0
- package/docs/specs/README.md +1 -0
- package/docs/specs/v0.0.9/README.md +9 -0
- package/docs/specs/v0.0.9/SPEC.md +68 -0
- package/package.json +3 -2
- package/src/cli.mjs +3 -0
- package/src/i18n.mjs +3 -0
- package/src/install.mjs +84 -16
|
@@ -84,15 +84,15 @@ peça ao agente para comparar as fontes e planejar o reparo com `init`/`sync`.
|
|
|
84
84
|
Entradas humanas permanecem separadas das geradas. Na migração de versões
|
|
85
85
|
antigas, o kit retira somente seu bloco reconhecido e preserva o texto humano.
|
|
86
86
|
Não altere os marcadores dos blocos que ainda serão migrados.
|
|
87
|
-
|
|
87
|
+
Links humanos preexistentes nas entradas e nos clientes são mantidos sem
|
|
88
|
+
atravessá-los. Consulte `preserved` no JSON para ver as rotas que não receberam
|
|
89
|
+
apontamentos. Não transforme o hub ou caminhos já gerenciados em links.
|
|
88
90
|
Destinos dentro de `.git`, `node_modules` ou `.pbai4devs` são recusados,
|
|
89
91
|
inclusive com variações de maiúsculas/minúsculas.
|
|
90
92
|
|
|
91
|
-
Na CLI
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
extra nas áreas de skills, agents, commands e rules. Prefira a CLI para manter
|
|
95
|
-
a instalação; não trate esses dois comandos como equivalentes.
|
|
93
|
+
Na CLI e no script `sincronizar-agentes`, `--force` repara conflitos gerenciados
|
|
94
|
+
com backup. Recursos nativos humanos e links preservados continuam intactos.
|
|
95
|
+
Para manter a instalação e sua seleção de clientes, prefira a CLI.
|
|
96
96
|
|
|
97
97
|
## Download, plataforma ou ferramenta falhando
|
|
98
98
|
|
package/docs/guides/workflow.md
CHANGED
|
@@ -41,6 +41,8 @@ documentos, não comprova comportamento e não autoriza substituir decisões.
|
|
|
41
41
|
|
|
42
42
|
## 2. Reunir as perguntas no início
|
|
43
43
|
|
|
44
|
+
Antes de criar documentos, [inventarie e leia o que existe](documentation.md).
|
|
45
|
+
Reutilize os caminhos e complemente lacunas, sem substituir pelo template.
|
|
44
46
|
Antes de escrever documentos ou código, leia o contexto já disponível.
|
|
45
47
|
Reúna as dúvidas que alteram objetivo, escopo, dependências ou critérios.
|
|
46
48
|
Não pergunte novamente o que o usuário já decidiu ou o projeto já registra.
|
|
@@ -64,7 +64,7 @@ existir; a primeira publicação pode usar o secret `NPMJS_TOKEN`.
|
|
|
64
64
|
Para publicar somente o pacote de uma tag que já tem artefato aprovado:
|
|
65
65
|
|
|
66
66
|
```console
|
|
67
|
-
gh workflow run release.yml --ref master -f tag=v0.0.
|
|
67
|
+
gh workflow run release.yml --ref master -f tag=v0.0.9 -F package_only=true
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Se o artefato expirou, reexecute o CI original da tag antes da publicação.
|
package/docs/product/design.md
CHANGED
|
@@ -66,6 +66,18 @@ O estado registra versão, escolha e propriedade local. Índices e manifesto
|
|
|
66
66
|
podem ser reconstruídos. Downloads, credenciais e relatórios ficam fora do
|
|
67
67
|
pacote e do Git.
|
|
68
68
|
|
|
69
|
+
## Inventário e evolução documental
|
|
70
|
+
|
|
71
|
+
O inspetor inclui metadados de documentação no JSON: caminhos, tipo, papel
|
|
72
|
+
provável, origem da indicação, tamanho e hash quando couber nos limites.
|
|
73
|
+
Ele não escreve arquivos, não emite conteúdo e não lê destinos simbólicos.
|
|
74
|
+
Limites e avisos impedem interpretar uma busca parcial como ausência de docs.
|
|
75
|
+
|
|
76
|
+
A skill `atualizar-documentacao` usa esse inventário antes dos templates.
|
|
77
|
+
O agente lê o material, propõe ações por arquivo e aplica complementos
|
|
78
|
+
autorizados. Organização usa índices e links, preservando caminhos e conteúdo.
|
|
79
|
+
Produto e aprovações dependem das decisões reais do usuário.
|
|
80
|
+
|
|
69
81
|
## Runtime
|
|
70
82
|
|
|
71
83
|
O launcher aceita Node 18+ e prepara Node 24+ antes de carregar o núcleo.
|
package/docs/product/prd.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Requisitos da distribuição portátil AgentDevKit.
|
|
3
3
|
status: aprovado
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.9
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# PRD do AgentDevKit
|
|
@@ -40,6 +40,9 @@ extraído do projeto de origem como produto independente.
|
|
|
40
40
|
| FR-018 | Publicar autoria PathBit, palavras-chave e licença MIT no npm. |
|
|
41
41
|
| FR-019 | Exibir README legível no npm sem alterar o Markdown fonte. |
|
|
42
42
|
| FR-020 | Levar a licença do kit ao hub sem alterar a licença do projeto. |
|
|
43
|
+
| FR-021 | Inventariar documentos existentes antes de propor alterações. |
|
|
44
|
+
| FR-022 | Complementar documentação sem substituir arquivos por templates. |
|
|
45
|
+
| FR-023 | Provar o fluxo documental em projetos reais e em projeto vazio. |
|
|
43
46
|
|
|
44
47
|
Execuções repetidas devem ser idempotentes; a CLI oferece simulação e JSON.
|
|
45
48
|
As instruções permanecem concisas, com detalhes opcionais em referências.
|
package/docs/product/roadmap.md
CHANGED
|
@@ -19,6 +19,7 @@ status: vigente
|
|
|
19
19
|
| Apresentação e licença v0.0.6 | Distribuição e atualização | [Spec][public]. |
|
|
20
20
|
| Compatibilidade real v0.0.7 | Testes em clones existentes | [Spec][oss]. |
|
|
21
21
|
| Preservação v0.0.8 | Auditoria e regressões | [Spec][robustness]. |
|
|
22
|
+
| Coexistência v0.0.9 | Agentes, documentos e projetos reais | [Spec][coexistence]. |
|
|
22
23
|
|
|
23
24
|
A primeira entrega segue a [spec v0.0.1](../specs/v0.0.1/SPEC.md), disponível
|
|
24
25
|
na [release v0.0.1][release]. Os detalhes de mudanças estão no
|
|
@@ -40,3 +41,4 @@ comportamento existente, sem alterar a versão de uma release já publicada.
|
|
|
40
41
|
[public]: ../specs/v0.0.6/SPEC.md
|
|
41
42
|
[oss]: ../specs/v0.0.7/SPEC.md
|
|
42
43
|
[robustness]: ../specs/v0.0.8/SPEC.md
|
|
44
|
+
[coexistence]: ../specs/v0.0.9/SPEC.md
|
package/docs/specs/README.md
CHANGED
|
@@ -13,6 +13,7 @@ description: Incrementos verificáveis do AgentDevKit.
|
|
|
13
13
|
- [v0.0.6](v0.0.6/SPEC.md): apresentação pública, licença e atualização segura.
|
|
14
14
|
- [v0.0.7](v0.0.7/SPEC.md): projetos reais e proteção local de artefatos.
|
|
15
15
|
- [v0.0.8](v0.0.8/SPEC.md): preservação, transações e validação aprofundada.
|
|
16
|
+
- [v0.0.9](v0.0.9/SPEC.md): coexistência e evolução documental sem perdas.
|
|
16
17
|
|
|
17
18
|
Cada incremento vincula requisitos, critérios, testes e evidências locais.
|
|
18
19
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Coexistência com agentes e evolução da documentação existente.
|
|
3
|
+
status: aprovado
|
|
4
|
+
version: 0.0.9
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Preservar agentes e documentação existentes
|
|
8
|
+
|
|
9
|
+
## Objetivo
|
|
10
|
+
|
|
11
|
+
Instalar e atualizar o AgentDevKit no Zod e em projetos com agentes próprios,
|
|
12
|
+
preservando configurações, arquivos, permissões, links e estado do Git.
|
|
13
|
+
Orientar também a evolução documental a partir do inventário e da leitura
|
|
14
|
+
dos materiais existentes, sem reiniciar o projeto. O pedido atual substitui
|
|
15
|
+
o bloqueio conservador documentado na
|
|
16
|
+
[versão anterior](../v0.0.8/SPEC.md).
|
|
17
|
+
|
|
18
|
+
## Contrato
|
|
19
|
+
|
|
20
|
+
- Tratar links simbólicos humanos nas entradas e nos clientes como caminhos
|
|
21
|
+
preservados: não abrir, atravessar, converter, sobrescrever ou adotar.
|
|
22
|
+
- Preservar agentes e skills humanos, inclusive nomes iguais aos do kit e
|
|
23
|
+
durante execução com `--force`. Informar os caminhos preservados.
|
|
24
|
+
- Gerar os apontamentos disponíveis sem atribuir ao kit arquivos preservados.
|
|
25
|
+
No Zod, manter `.codex/skills` e usar também `.agents/skills` para o Codex.
|
|
26
|
+
- Manter a validação estrita em fontes, estado, ferramentas, Git e caminhos
|
|
27
|
+
antes pertencentes ao kit. Um link não pode esconder alteração gerenciada.
|
|
28
|
+
- Manter os mesmos contratos na CLI e no sincronizador instalado.
|
|
29
|
+
- Antes de editar documentos, inventariar e ler os existentes. Reaproveitar
|
|
30
|
+
caminhos, seções e decisões; templates orientam somente lacunas.
|
|
31
|
+
- Organizar por índices e links, sem apagar, mover ou substituir documentos.
|
|
32
|
+
Em projetos novos, não inventar produto ou aprovação sem respostas.
|
|
33
|
+
|
|
34
|
+
## Critérios de aceite
|
|
35
|
+
|
|
36
|
+
| ID | Resultado observável |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| AC-001 | Links humanos internos, externos e quebrados permanecem intactos. |
|
|
39
|
+
| AC-002 | Skills, agentes e configurações preexistentes são preservados. |
|
|
40
|
+
| AC-003 | Caminhos gerenciados recusam links sem escrita externa. |
|
|
41
|
+
| AC-004 | Init, update, doctor e sync são coerentes e idempotentes. |
|
|
42
|
+
| AC-005 | Zod, Express e Chi passam na matriz do pacote, preservando Git. |
|
|
43
|
+
| AC-006 | Regressões, documentação, CI e pacote público são verificados. |
|
|
44
|
+
| AC-007 | Inventário detecta documentação real sem alterar arquivos. |
|
|
45
|
+
| AC-008 | Skills exigem leitura e plano aditivo antes dos templates. |
|
|
46
|
+
| AC-009 | Exercícios documentais preservam conteúdo e arquivos fora do plano. |
|
|
47
|
+
| AC-010 | Projeto vazio recebe o kit sem documentos de produto inventados. |
|
|
48
|
+
|
|
49
|
+
## Validação
|
|
50
|
+
|
|
51
|
+
Executar testes de regressão para links e colisões, inclusive `--force`,
|
|
52
|
+
substituição de caminhos após o plano e mudança de seleção dos clientes.
|
|
53
|
+
Rodar pipeline completo, maturidade e matriz OSS com o TGZ candidato;
|
|
54
|
+
repetir a matriz com o pacote publicado. Comparar bytes, tipos, modos,
|
|
55
|
+
destinos de links, staging e configuração Git nos clones.
|
|
56
|
+
Execute também o inventário instalado e o fluxo de documentação em clones
|
|
57
|
+
reais: plano por arquivo, complemento baseado em fatos, índice novo e revisão.
|
|
58
|
+
Separe essa prova da simples instalação e de fixtures do harness; não atribua
|
|
59
|
+
ao pacote geração automática de conteúdo ou aprovações de produto.
|
|
60
|
+
|
|
61
|
+
Clones e evidências ficam ignorados em `tmp/` e `.agent-tests/`.
|
|
62
|
+
O escopo é o kit e seu fluxo documental; não afirmar execução das aplicações
|
|
63
|
+
clonadas.
|
|
64
|
+
|
|
65
|
+
## Documentação afetada
|
|
66
|
+
|
|
67
|
+
Atualizar instalação, CLI, recuperação, recursos, sincronização, testes,
|
|
68
|
+
changelog e índices. Preservar as specs antigas como histórico.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pathbit/agentdevkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Shared skills, specifications and workflows for AI coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"ci:full": "node scripts/release/pipeline.mjs full",
|
|
52
52
|
"ci:portable": "node scripts/release/pipeline.mjs portable",
|
|
53
53
|
"ci:smoke": "node scripts/release/pipeline.mjs smoke",
|
|
54
|
-
"test:oss": "node tests/oss-compatibility.mjs"
|
|
54
|
+
"test:oss": "node tests/oss-compatibility.mjs",
|
|
55
|
+
"test:docs": "node tests/documentation-compatibility.mjs"
|
|
55
56
|
},
|
|
56
57
|
"author": {
|
|
57
58
|
"name": "PathBit",
|
package/src/cli.mjs
CHANGED
|
@@ -62,6 +62,7 @@ function print(result, options, output) {
|
|
|
62
62
|
if (result.clients) output.write(`${text(result.language, 'clientsCurrent')}: ${result.clients.join(', ')}\n`);
|
|
63
63
|
if (result.availableClients) output.write(`${text(result.language, 'clientsAvailable')}: ${result.availableClients.map(client => client.id).join(', ')}\n`);
|
|
64
64
|
if (result.toolOutput) output.write(`${result.toolOutput.trimEnd()}\n`);
|
|
65
|
+
if (result.preserved?.length) output.write(`${text(result.language, 'preserved')}: ${result.preserved.length}. ${text(result.language, 'preservedDetails')}\n`);
|
|
65
66
|
if (result.plan?.length) {
|
|
66
67
|
const names = { pt: { create: 'criar', update: 'atualizar', remove: 'remover' }, en: { create: 'create', update: 'update', remove: 'remove' }, es: { create: 'crear', update: 'actualizar', remove: 'eliminar' } };
|
|
67
68
|
const labels = {
|
|
@@ -123,6 +124,7 @@ export async function main(argv = process.argv.slice(2), context = {}) {
|
|
|
123
124
|
const found = inspect(target);
|
|
124
125
|
if (found.installed && !found.issues.length) {
|
|
125
126
|
const plan = planInstallation({ target, language, mode: 'sync' });
|
|
127
|
+
result.preserved = plan.preserved;
|
|
126
128
|
found.issues.push(...plan.changes.map(change => ({ code: 'outdated', path: change.path })));
|
|
127
129
|
found.issues.push(...plan.localIgnoreCheck.missing.map(path => ({ code: 'unignored', path })));
|
|
128
130
|
if (plan.localIgnore.edits.length || plan.untrack?.paths.length) found.issues.push({ code: 'local-ignore', path: '.gitignore / Git info/exclude' });
|
|
@@ -144,6 +146,7 @@ export async function main(argv = process.argv.slice(2), context = {}) {
|
|
|
144
146
|
result.next = getNextSteps({ command: options.command, language, target, installed: Boolean(previous) });
|
|
145
147
|
}
|
|
146
148
|
const plan = planInstallation({ target, language, mode: options.command, force: options.force, clients: options.clients });
|
|
149
|
+
result.preserved = plan.preserved;
|
|
147
150
|
result.plan = plan.changes.map(({ path, action }) => ({ path, action, conflict: plan.conflicts.includes(path) }));
|
|
148
151
|
result.untrackedPaths = [...(plan.untrack?.paths || [])];
|
|
149
152
|
result.localIgnore = { status: plan.localIgnore.status, edits: plan.localIgnore.edits.map(({ path, before }) => ({ path, action: before === null ? 'create' : 'update' })), missing: plan.localIgnoreCheck.missing };
|
package/src/i18n.mjs
CHANGED
|
@@ -7,6 +7,7 @@ export function normalizeLanguage(value) {
|
|
|
7
7
|
|
|
8
8
|
const messages = {
|
|
9
9
|
pt: {
|
|
10
|
+
preserved: 'Caminhos existentes preservados', preservedDetails: 'Use --json para consultar os caminhos e motivos.',
|
|
10
11
|
help: 'AgentDevKit: init [diretório], update, doctor, sync [--check], lang [pt|en|es], agents [lista], next, tools <ação>.\nOpções: --target <diretório>, --lang <idioma>, --agents <lista>, --yes, --dry-run, --force, --json.\nAliases: --clients e clients.',
|
|
11
12
|
clientsCurrent: 'Agentes selecionados', clientsAvailable: 'Agentes disponíveis', clientsSelect: 'Agentes (IDs separados por vírgula ou all) [all]: ', agents: 'Seleção de agentes atualizada.',
|
|
12
13
|
next: 'Próximo passo', init: 'Estrutura instalada.', update: 'Estrutura atualizada.', sync: 'Apontamentos sincronizados.', lang: 'Idioma configurado.', unchanged: 'Estrutura já atualizada.', plan: 'Plano calculado; nenhum arquivo alterado.', conflict: 'Há arquivos próprios ou alterados em conflito. Revise o plano; --force salva backup antes de substituir.',
|
|
@@ -15,6 +16,7 @@ const messages = {
|
|
|
15
16
|
agentLanguage: 'Responda em português, salvo quando o usuário solicitar outro idioma.', inspect: 'Verifique a instalação', workflow: 'Consulte a próxima etapa', install: 'Inicialize este projeto', repair: 'Confira a sincronização', review: 'Revise os arquivos em conflito', tools: 'Resultado da ferramenta',
|
|
16
17
|
},
|
|
17
18
|
en: {
|
|
19
|
+
preserved: 'Existing paths preserved', preservedDetails: 'Use --json to inspect paths and reasons.',
|
|
18
20
|
help: 'AgentDevKit: init [directory], update, doctor, sync [--check], lang [pt|en|es], agents [list], next, tools <action>.\nOptions: --target <directory>, --lang <language>, --agents <list>, --yes, --dry-run, --force, --json.\nAliases: --clients and clients.',
|
|
19
21
|
clientsCurrent: 'Selected agents', clientsAvailable: 'Available agents', clientsSelect: 'Agents (comma-separated IDs or all) [all]: ', agents: 'Agent selection updated.',
|
|
20
22
|
next: 'Next step', init: 'Structure installed.', update: 'Structure updated.', sync: 'Pointers synchronized.', lang: 'Language configured.', unchanged: 'Structure is already current.', plan: 'Plan calculated; no files changed.', conflict: 'User files or edits conflict with this plan. Review it; --force backs them up before replacement.',
|
|
@@ -23,6 +25,7 @@ const messages = {
|
|
|
23
25
|
agentLanguage: 'Respond in English unless the user requests another language.', inspect: 'Check the installation', workflow: 'Review the next stage', install: 'Initialize this project', repair: 'Check synchronization', review: 'Review conflicting files', tools: 'Tool result',
|
|
24
26
|
},
|
|
25
27
|
es: {
|
|
28
|
+
preserved: 'Rutas existentes preservadas', preservedDetails: 'Use --json para consultar las rutas y los motivos.',
|
|
26
29
|
help: 'AgentDevKit: init [directorio], update, doctor, sync [--check], lang [pt|en|es], agents [lista], next, tools <acción>.\nOpciones: --target <directorio>, --lang <idioma>, --agents <lista>, --yes, --dry-run, --force, --json.\nAlias: --clients y clients.',
|
|
27
30
|
clientsCurrent: 'Agentes seleccionados', clientsAvailable: 'Agentes disponibles', clientsSelect: 'Agentes (IDs separados por coma o all) [all]: ', agents: 'Selección de agentes actualizada.',
|
|
28
31
|
next: 'Próximo paso', init: 'Estructura instalada.', update: 'Estructura actualizada.', sync: 'Referencias sincronizadas.', lang: 'Idioma configurado.', unchanged: 'La estructura ya está actualizada.', plan: 'Plan calculado; ningún archivo modificado.', conflict: 'Hay archivos propios o cambios en conflicto. Revise el plan; --force guarda una copia antes de reemplazarlos.',
|
package/src/install.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { synchronize, isReference } from '../.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs';
|
|
6
6
|
import { isGeneratedOutput, planLocalIgnore, checkLocalIgnore, assertLocalIgnoreUnchanged, applyLocalIgnore, planUntrack, applyUntrack, restoreUntrack } from '../.pbai4devs/scripts/lib/gitignore.mjs';
|
|
7
|
+
import { nativePathBoundary } from '../.pbai4devs/scripts/lib/native-paths.mjs';
|
|
7
8
|
import { writeAdapterManifest, adapterRoots, normalizeClients, resolveAdapters } from './adapters/index.mjs';
|
|
8
9
|
import { packageRoot, packageInfo, statePath, entryNames, begin, end } from './config.mjs';
|
|
9
10
|
import { normalizeLanguage, text } from './i18n.mjs';
|
|
@@ -173,6 +174,24 @@ function copyResources(source, destination) {
|
|
|
173
174
|
return files;
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
function readDerivedState(target) {
|
|
178
|
+
let entries = {}, outputs = {};
|
|
179
|
+
const derived = read(target, '.pbai4devs/estado/derived.json');
|
|
180
|
+
if (derived) {
|
|
181
|
+
try {
|
|
182
|
+
const value = JSON.parse(derived);
|
|
183
|
+
if (value.schemaVersion !== 1 || !value.entries || typeof value.entries !== 'object' || Array.isArray(value.entries)
|
|
184
|
+
|| value.outputs !== undefined && (!value.outputs || typeof value.outputs !== 'object' || Array.isArray(value.outputs))) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
185
|
+
entries = value.entries; outputs = value.outputs ?? {};
|
|
186
|
+
for (const [path, digest] of Object.entries(entries)) if (!entryNames.includes(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
187
|
+
for (const [path, digest] of Object.entries(outputs)) if (!allowedManaged(path) || !/^\.[^/]+\/(?:skills|agents|rules)\.json$/.test(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived output state.');
|
|
188
|
+
}
|
|
189
|
+
catch { fail('E_STATE', 'Invalid derived entrypoint state.'); }
|
|
190
|
+
for (const path of [...Object.keys(entries), ...Object.keys(outputs)]) safePath(target, path);
|
|
191
|
+
}
|
|
192
|
+
return { entries, outputs };
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
export function planInstallation({ target, language = 'pt', mode = 'init', force = false, clients }) {
|
|
177
196
|
const previous = loadState(target);
|
|
178
197
|
if (mode !== 'init' && !previous) fail('E_MISSING', text(language, 'missing'));
|
|
@@ -180,16 +199,37 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
180
199
|
const active = resolveAdapters(selected);
|
|
181
200
|
const changingClients = clients !== undefined || mode === 'agents';
|
|
182
201
|
const protectedEntries = new Set(), entryConflicts = [], ownedEntrypoints = new Set();
|
|
202
|
+
const { entries: derivedEntries, outputs: derivedOutputs } = readDerivedState(target);
|
|
203
|
+
const ownedPaths = [...Object.keys(previous?.files || {}), ...Object.keys(previous?.blocks || {}), ...Object.keys(derivedEntries), ...Object.keys(derivedOutputs)];
|
|
204
|
+
const preserved = new Map();
|
|
205
|
+
const recordPreserved = (path, reason) => { preserved.set(path, { path, reason }); return true; };
|
|
206
|
+
const native = path => entryNames.includes(path) || path === 'README.md' || adapterRoots.some(root => path === root || path.startsWith(`${root}/`));
|
|
207
|
+
const boundary = path => {
|
|
208
|
+
if (!native(path)) return false;
|
|
209
|
+
const found = nativePathBoundary(target, path, ownedPaths, { preserveRootAliases: path === 'README.md' || entryNames.includes(path) });
|
|
210
|
+
return found ? recordPreserved(found.path, found.reason) : false;
|
|
211
|
+
};
|
|
212
|
+
const owned = path => ownedPaths.includes(path);
|
|
213
|
+
const generatedReference = content => Boolean(content && isReference(content.toString('utf8'))
|
|
214
|
+
&& (content.toString('utf8').split(/\r?\n/).includes('<!-- gerado: sincronizar-agentes -->') || content.toString('utf8').startsWith('# gerado: sincronizar-agentes\n')));
|
|
215
|
+
const preserveNative = path => {
|
|
216
|
+
if (!native(path)) return false;
|
|
217
|
+
if (boundary(path)) return true;
|
|
218
|
+
const item = stat(safePath(target, path));
|
|
219
|
+
if (!item || owned(path)) return false;
|
|
220
|
+
if (!item.isFile() && ownedPaths.some(value => value.startsWith(`${path}/`))) fail('E_PATH', `Managed subtree occupies a file destination: ${path}`);
|
|
221
|
+
if (item.isFile()) {
|
|
222
|
+
const current = read(target, path);
|
|
223
|
+
if (generatedReference(current)) return false;
|
|
224
|
+
if (path.endsWith('/.gitignore') && current.toString('utf8').split(/\r?\n/).some(line => ['# BEGIN AGENT IGNORE', '# END AGENT IGNORE'].includes(line))) return false;
|
|
225
|
+
}
|
|
226
|
+
return recordPreserved(path, item.isFile() ? 'existing-file' : 'existing-directory');
|
|
227
|
+
};
|
|
183
228
|
for (const name of entryNames) {
|
|
229
|
+
if (preserveNative(name)) continue;
|
|
184
230
|
const current = read(target, name);
|
|
185
231
|
if (current && previous?.files[name] && previous.files[name] !== hash(current)) { protectedEntries.add(name); entryConflicts.push(name); }
|
|
186
232
|
}
|
|
187
|
-
let derivedEntries = {};
|
|
188
|
-
const derived = read(target, '.pbai4devs/estado/derived.json');
|
|
189
|
-
if (derived) {
|
|
190
|
-
try { const value = JSON.parse(derived); if (value.schemaVersion === 1 && value.entries && typeof value.entries === 'object') derivedEntries = value.entries; }
|
|
191
|
-
catch { fail('E_STATE', 'Invalid derived entrypoint state.'); }
|
|
192
|
-
}
|
|
193
233
|
const stage = realpathSync(mkdtempSync(join(tmpdir(), 'agentdevkit-')));
|
|
194
234
|
try {
|
|
195
235
|
const existing = copyResources(join(target, '.pbai4devs'), join(stage, '.pbai4devs'));
|
|
@@ -206,6 +246,7 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
206
246
|
if (path.startsWith('.pbai4devs/') && !supplied.has(path.slice(11)) && !internal.has(path.slice(11))) rmSync(safePath(stage, path), { force: true });
|
|
207
247
|
}
|
|
208
248
|
for (const root of active.roots) {
|
|
249
|
+
if (preserveNative(`${root}/.gitignore`)) continue;
|
|
209
250
|
const content = read(target, `${root}/.gitignore`);
|
|
210
251
|
if (content) write(stage, `${root}/.gitignore`, content);
|
|
211
252
|
}
|
|
@@ -214,7 +255,7 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
214
255
|
writeAdapterManifest(stage, { clients: selected });
|
|
215
256
|
const synchronized = synchronize({ root: stage, force: true, localIgnores: false, generateEntrypoints: false, output() {} });
|
|
216
257
|
for (const name of active.entrypoints) {
|
|
217
|
-
if (protectedEntries.has(name)) continue;
|
|
258
|
+
if (protectedEntries.has(name) || preserveNative(name)) continue;
|
|
218
259
|
const current = read(target, name), template = readFileSync(join(packageRoot, '.pbai4devs/templates/entrypoints', name), 'utf8');
|
|
219
260
|
const legacy = entryContent(null, template, language, name);
|
|
220
261
|
const content = legacy.replace(`${begin}\n`, '').replace(`\n${end}`, '');
|
|
@@ -222,11 +263,18 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
222
263
|
const knownLegacy = current && previous?.blocks[name] && current.toString('utf8') === entryContent(null, template, previous.language, name);
|
|
223
264
|
if (!current || knownFile || knownLegacy) { write(stage, name, content); ownedEntrypoints.add(name); }
|
|
224
265
|
}
|
|
225
|
-
if (!read(target, 'README.md')) write(stage, 'README.md', `---\ndescription: ${JSON.stringify(text(language, 'readme'))}\nlanguage: ${language}\n---\n\n# ${text(language, 'project')}\n\n${text(language, 'readme')}\n`);
|
|
266
|
+
if (!preserveNative('README.md') && !read(target, 'README.md')) write(stage, 'README.md', `---\ndescription: ${JSON.stringify(text(language, 'readme'))}\nlanguage: ${language}\n---\n\n# ${text(language, 'project')}\n\n${text(language, 'readme')}\n`);
|
|
226
267
|
if (!existsSync(join(stage, '.pbai4devs/estado/README.md'))) write(stage, '.pbai4devs/estado/README.md', `---\ndescription: AgentDevKit state.\n---\n\n# AgentDevKit\n\n${text(language, 'state')}\n`);
|
|
227
268
|
const unknown = new Set(existing.filter(path => !supplied.has(path) && !previous?.files[`.pbai4devs/${path}`] && !internal.has(path)));
|
|
228
269
|
const paths = walk(stage, '', path => !path.split('/').includes('.agent-tests')).filter(path => !path.startsWith('.pbai4devs/') || !unknown.has(path.slice(11)));
|
|
229
|
-
const desired = new Map(paths.map(path => [path, readFileSync(join(stage, path))]));
|
|
270
|
+
const desired = new Map(paths.filter(path => !preserveNative(path)).map(path => [path, readFileSync(join(stage, path))]));
|
|
271
|
+
// Native discovery manifests must not advertise resources omitted at a human boundary.
|
|
272
|
+
const overlapsPreserved = path => [...preserved.keys()].some(kept => path === kept || path.startsWith(`${kept}/`) || kept.startsWith(`${path}/`));
|
|
273
|
+
for (const [path, content] of desired) if (/^\.agents\/(skills|rules|agents)\.json$/.test(path)) {
|
|
274
|
+
const discovery = JSON.parse(content);
|
|
275
|
+
discovery.entries = discovery.entries.filter(entry => !overlapsPreserved(entry.path));
|
|
276
|
+
desired.set(path, Buffer.from(`${JSON.stringify(discovery, null, 2)}\n`));
|
|
277
|
+
}
|
|
230
278
|
const manifest = { schemaVersion: 1, version: bundled.length ? packageInfo.version : previous.version, language, files: {}, blocks: {} };
|
|
231
279
|
if (mode !== 'sync' || clients !== undefined || previous?.clients !== undefined) manifest.clients = selected;
|
|
232
280
|
for (const [path, content] of desired) {
|
|
@@ -240,7 +288,8 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
240
288
|
for (const [path, content] of desired) {
|
|
241
289
|
const current = read(target, path);
|
|
242
290
|
if (current?.equals(content)) continue;
|
|
243
|
-
const managed = previous?.blocks[path] ? blockDigest(path, current || Buffer.alloc(0)) === previous.blocks[path]
|
|
291
|
+
const managed = previous?.blocks[path] ? blockDigest(path, current || Buffer.alloc(0)) === previous.blocks[path]
|
|
292
|
+
: [previous?.files[path], derivedOutputs[path], derivedEntries[path]].some(digest => digest && hash(current || '') === digest);
|
|
244
293
|
const appendBlock = entryNames.includes(path) || path === '.gitignore' || path.endsWith('/.gitignore');
|
|
245
294
|
const conflict = current && path !== statePath && !managed && !appendBlock;
|
|
246
295
|
const changedBlock = current && previous?.blocks[path] && !managed;
|
|
@@ -270,11 +319,29 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
270
319
|
}
|
|
271
320
|
changes.push({ path, action: content.trim() ? 'update' : 'remove', content: content.trim() ? Buffer.from(content) : null, beforeHash: hash(current) });
|
|
272
321
|
}
|
|
273
|
-
|
|
322
|
+
function nativeFiles(root) {
|
|
323
|
+
if (boundary(root)) return [];
|
|
324
|
+
const directory = stat(safePath(target, root));
|
|
325
|
+
if (!directory?.isDirectory()) return [];
|
|
326
|
+
const files = [];
|
|
327
|
+
const visit = folder => {
|
|
328
|
+
for (const name of readdirSync(safePath(target, folder)).sort()) {
|
|
329
|
+
const path = `${folder}/${name}`;
|
|
330
|
+
if (boundary(path)) continue;
|
|
331
|
+
const info = stat(safePath(target, path));
|
|
332
|
+
if (path.split('/').some(part => ignored.has(part))) continue;
|
|
333
|
+
if (info?.isDirectory()) visit(path);
|
|
334
|
+
else if (info?.isFile()) files.push(path);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
visit(root); return files;
|
|
338
|
+
}
|
|
339
|
+
for (const root of changingClients ? [] : active.roots) for (const full of nativeFiles(root)) {
|
|
340
|
+
const path = full.slice(root.length + 1);
|
|
274
341
|
if (!['skills', 'agents', 'commands', 'rules', 'instructions', 'prompts'].includes(path.split('/')[0])) continue;
|
|
275
|
-
|
|
342
|
+
if (desired.has(full) || changes.some(change => change.path === full)) continue;
|
|
276
343
|
const current = read(target, full), source = current.toString('utf8');
|
|
277
|
-
if (!(source.includes('<!-- gerado: sincronizar-agentes -->') || source.startsWith('# gerado: sincronizar-agentes\n'))) continue;
|
|
344
|
+
if (!(source.includes('<!-- gerado: sincronizar-agentes -->') || source.startsWith('# gerado: sincronizar-agentes\n'))) { recordPreserved(full, 'existing-file'); continue; }
|
|
278
345
|
if (!isReference(source)) conflicts.push(full);
|
|
279
346
|
changes.push({ path: full, action: 'remove', content: null, beforeHash: hash(current) });
|
|
280
347
|
}
|
|
@@ -285,7 +352,7 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
285
352
|
changes.push({ path: legacy, action: 'remove', content: null, beforeHash: hash(legacyContent) });
|
|
286
353
|
}
|
|
287
354
|
const retiredRoots = adapterRoots.filter(root => !active.roots.includes(root) && changes.some(change => change.action === 'remove' && change.path.startsWith(`${root}/`) && (previous?.files[change.path] || previous?.blocks[change.path])));
|
|
288
|
-
const ownedOutputs = [...new Set([...synchronized.ownedOutputs, ...ownedEntrypoints, statePath])];
|
|
355
|
+
const ownedOutputs = [...new Set([...synchronized.ownedOutputs, ...ownedEntrypoints, statePath])].filter(path => !overlapsPreserved(path));
|
|
289
356
|
const hashes = {};
|
|
290
357
|
for (const [path, digest] of Object.entries(previous?.files || {})) if ((ownedOutputs.includes(path) || !path.startsWith('.pbai4devs/')) && !protectedEntries.has(path)) hashes[path] = digest;
|
|
291
358
|
for (const path of ownedEntrypoints) { const current = read(target, path); if (current && (previous?.blocks[path] || derivedEntries[path] === hash(current))) hashes[path] = hash(current); }
|
|
@@ -298,7 +365,7 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
298
365
|
const localIgnore = planLocalIgnore(target, { outputs: ownedOutputs, templateRoot: stage });
|
|
299
366
|
const localIgnoreCheck = checkLocalIgnore(localIgnore, { replacedBlocks: replacedIgnoreBlocks(target, changes, previous) });
|
|
300
367
|
conflicts.push(...(untrack?.conflicts || []), ...localIgnoreCheck.missing);
|
|
301
|
-
return { target, language, mode, changes, conflicts: [...new Set(conflicts)].sort(), canApply: (!conflicts.length || force) && !untrack?.conflicts?.length && !localIgnoreCheck.missing.length, manifest, retiredRoots, ownedOutputs, untrack, localIgnore, localIgnoreCheck };
|
|
368
|
+
return { target, language, mode, changes, conflicts: [...new Set(conflicts)].sort(), canApply: (!conflicts.length || force) && !untrack?.conflicts?.length && !localIgnoreCheck.missing.length, manifest, retiredRoots, ownedOutputs, untrack, localIgnore, localIgnoreCheck, preserved: [...preserved.values()].sort((a, b) => a.path.localeCompare(b.path)) };
|
|
302
369
|
} finally { rmSync(stage, { recursive: true, force: true }); }
|
|
303
370
|
}
|
|
304
371
|
|
|
@@ -399,10 +466,11 @@ function pruneRemovedDirectories(plan, onRemoved) {
|
|
|
399
466
|
export function inspect(target) {
|
|
400
467
|
const state = loadState(target);
|
|
401
468
|
if (!state) return { installed: false, issues: [{ code: 'missing', path: statePath }] };
|
|
469
|
+
const derived = readDerivedState(target);
|
|
402
470
|
const issues = [];
|
|
403
471
|
for (const [path, digest] of Object.entries(state.files)) {
|
|
404
472
|
const content = read(target, path);
|
|
405
|
-
if (!content || hash(content) !== digest) issues.push({ code: content ? 'modified' : 'missing', path });
|
|
473
|
+
if (!content || hash(content) !== digest && hash(content) !== derived.outputs[path]) issues.push({ code: content ? 'modified' : 'missing', path });
|
|
406
474
|
}
|
|
407
475
|
for (const [path, digest] of Object.entries(state.blocks)) {
|
|
408
476
|
const content = read(target, path);
|