@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.
Files changed (51) hide show
  1. package/.pbai4devs/COMPATIBILIDADE.md +4 -2
  2. package/.pbai4devs/INSTRUCTIONS.md +4 -0
  3. package/.pbai4devs/commands/sincronizar/COMMAND.md +4 -3
  4. package/.pbai4devs/rules/documentacao/RULE.md +4 -0
  5. package/.pbai4devs/rules/qualidade/RULE.md +4 -2
  6. package/.pbai4devs/scripts/README.md +3 -3
  7. package/.pbai4devs/scripts/inspecionar-projeto/README.md +31 -0
  8. package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +142 -1
  9. package/.pbai4devs/scripts/inspecionar-projeto/tests/TEST.mjs +168 -1
  10. package/.pbai4devs/scripts/lib/native-paths.mjs +35 -0
  11. package/.pbai4devs/scripts/sincronizar-agentes/README.md +3 -0
  12. package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +73 -29
  13. package/.pbai4devs/scripts/sincronizar-agentes/tests/COEXISTENCE.mjs +190 -0
  14. package/.pbai4devs/scripts/sincronizar-agentes/tests/HUMAN_OWNERSHIP.mjs +8 -2
  15. package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +28 -28
  16. package/.pbai4devs/scripts/verificar-recursos/SCRIPT.mjs +56 -8
  17. package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +92 -1
  18. package/.pbai4devs/skills/produto/criar-design-doc/SKILL.md +2 -0
  19. package/.pbai4devs/skills/produto/criar-prd/SKILL.md +2 -0
  20. package/.pbai4devs/skills/produto/criar-ux-ui/SKILL.md +2 -0
  21. package/.pbai4devs/skills/projeto/atualizar-documentacao/SKILL.md +18 -4
  22. package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +2 -0
  23. package/.pbai4devs/skills/projeto/criar-recurso/SKILL.md +1 -0
  24. package/.pbai4devs/skills/projeto/sincronizar-agentes/SKILL.md +3 -2
  25. package/.pbai4devs/skills/specs/criar-spec/SKILL.md +1 -0
  26. package/.pbai4devs/skills/specs/gerenciar-roadmap/SKILL.md +2 -1
  27. package/.pbai4devs/templates/README.md +4 -1
  28. package/.pbai4devs/templates/configuracao/TEMPLATE.md +6 -0
  29. package/CHANGELOG.md +10 -0
  30. package/README.en.md +3 -2
  31. package/README.es.md +3 -2
  32. package/README.md +3 -2
  33. package/docs/README.md +1 -0
  34. package/docs/guides/README.md +1 -0
  35. package/docs/guides/cli.md +2 -0
  36. package/docs/guides/documentation.md +63 -0
  37. package/docs/guides/installation.md +12 -5
  38. package/docs/guides/resources.md +6 -5
  39. package/docs/guides/troubleshooting.md +6 -6
  40. package/docs/guides/workflow.md +2 -0
  41. package/docs/maintainers/packages.md +1 -1
  42. package/docs/product/design.md +12 -0
  43. package/docs/product/prd.md +4 -1
  44. package/docs/product/roadmap.md +2 -0
  45. package/docs/specs/README.md +1 -0
  46. package/docs/specs/v0.0.9/README.md +9 -0
  47. package/docs/specs/v0.0.9/SPEC.md +68 -0
  48. package/package.json +3 -2
  49. package/src/cli.mjs +3 -0
  50. package/src/i18n.mjs +3 -0
  51. 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
- Não transforme diretórios de agentes ou o hub em links simbólicos.
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, `sync --force` substitui conflitos identificados, com backup, e
92
- preserva arquivos nativos próprios que não são do kit. O script compartilhado
93
- `sincronizar-agentes` tem um modo `--force` mais amplo: pode limpar conteúdo
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
 
@@ -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.8 -F package_only=true
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.
@@ -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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Requisitos da distribuição portátil AgentDevKit.
3
3
  status: aprovado
4
- version: 0.0.6
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.
@@ -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
@@ -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,9 @@
1
+ ---
2
+ description: Preservação de agentes e evolução da documentação existente.
3
+ ---
4
+
5
+ # Versão 0.0.9
6
+
7
+ [Spec](SPEC.md): preservar agentes e complementar documentos existentes.
8
+
9
+ [Specs](../README.md) · [Changelog](../../../CHANGELOG.md)
@@ -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.8",
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] : previous?.files[path] ? hash(current || '') === previous.files[path] : false;
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
- for (const root of changingClients ? [] : active.roots) for (const path of walk(join(target, root), '', path => !path.split('/').some(part => ignored.has(part)))) {
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
- const full = `${root}/${path}`; if (desired.has(full) || changes.some(change => change.path === full)) continue;
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);