@pathbit/agentdevkit 0.0.8 → 0.0.10

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 (58) 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 +18 -0
  30. package/README.en.md +18 -5
  31. package/README.es.md +19 -6
  32. package/README.md +16 -3
  33. package/docs/README.md +4 -2
  34. package/docs/guides/README.md +3 -1
  35. package/docs/guides/cli.md +5 -0
  36. package/docs/guides/documentation.md +63 -0
  37. package/docs/guides/installation.md +71 -14
  38. package/docs/guides/prerequisites.md +123 -0
  39. package/docs/guides/resources.md +6 -5
  40. package/docs/guides/tools-and-testing.md +2 -1
  41. package/docs/guides/troubleshooting.md +29 -13
  42. package/docs/guides/workflow.md +2 -0
  43. package/docs/maintainers/README.md +1 -1
  44. package/docs/maintainers/maintenance.md +8 -4
  45. package/docs/maintainers/packages.md +45 -13
  46. package/docs/product/design.md +12 -0
  47. package/docs/product/prd.md +4 -1
  48. package/docs/product/roadmap.md +4 -0
  49. package/docs/specs/README.md +3 -0
  50. package/docs/specs/dual-registry/SPEC.md +43 -0
  51. package/docs/specs/v0.0.10/README.md +9 -0
  52. package/docs/specs/v0.0.10/SPEC.md +42 -0
  53. package/docs/specs/v0.0.9/README.md +9 -0
  54. package/docs/specs/v0.0.9/SPEC.md +68 -0
  55. package/package.json +3 -2
  56. package/src/cli.mjs +3 -0
  57. package/src/i18n.mjs +3 -0
  58. package/src/install.mjs +84 -16
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: Mesma versão pública no npm e GitHub Packages e limpeza do histórico falho.
3
+ status: aprovado
4
+ ---
5
+
6
+ # Publicação sincronizada
7
+
8
+ ## Objetivo
9
+
10
+ Remover as execuções antigas com erro, preservando execuções aprovadas e
11
+ releases publicadas. Publicar o mesmo AgentDevKit no npm e GitHub Packages,
12
+ sem mudar o número ou o conteúdo de uma versão já publicada.
13
+
14
+ ## Contrato
15
+
16
+ - Excluir somente execuções concluídas com falha confirmada.
17
+ - Preservar tags, releases, assets e execuções aprovadas.
18
+ - Usar o TGZ original aprovado pelo CI nos dois registries.
19
+ - Manter `npx` pelo npm público sem autenticação como opção padrão.
20
+ - Autenticar o GitHub com token próprio, sem compartilhar credenciais.
21
+ - Confirmar visibilidade pública, versão e integridade nos dois destinos.
22
+ - Publicar ambos nas próximas tags; permitir completar só um destino.
23
+ - Recusar uma versão existente com bytes diferentes; nunca reempacotar a tag.
24
+
25
+ ## Critérios de aceite
26
+
27
+ | ID | Prova |
28
+ | --- | --- |
29
+ | AC-001 | Execuções falhas removidas; aprovadas e releases preservadas. |
30
+ | AC-002 | Mesma versão e mesmo checksum nos dois registries. |
31
+ | AC-003 | GitHub público e associado ao repositório correto. |
32
+ | AC-004 | Tokens isolados e acesso anônimo ao npm preservado. |
33
+ | AC-005 | Automação publica ambos e permite retomar um destino. |
34
+ | AC-006 | Testes e revisão aprovados; consumo dos dois destinos verificado. |
35
+
36
+ ## Validação
37
+
38
+ Comparar inventários remotos antes/depois da limpeza. Testar perfis de registry,
39
+ erros de autenticação, integridade divergente, repetição e seleção do workflow.
40
+ Completar a versão `0.0.9` no GitHub com seu artefato original, comparar os
41
+ registries e provar instalação, diagnóstico e sincronização.
42
+
43
+ Provas ficam em `tmp/` ou `.agent-tests/`, ignoradas pelo Git.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Distribuição pelos dois registries e preparo dos pré-requisitos.
3
+ ---
4
+
5
+ # Versão 0.0.10
6
+
7
+ [Spec](SPEC.md): publicar e verificar os dois caminhos de instalação.
8
+
9
+ [Specs](../README.md) · [Changelog](../../../CHANGELOG.md)
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: Publicação da documentação de instalação e da distribuição nos dois registries.
3
+ status: aprovado
4
+ version: 0.0.10
5
+ ---
6
+
7
+ # Publicar e verificar os dois caminhos de instalação
8
+
9
+ ## Objetivo
10
+
11
+ Publicar a versão 0.0.10 com os guias de pré-requisitos e instalação já
12
+ aprovados. Disponibilizar os mesmos bytes no npm e GitHub Packages e provar
13
+ o consumo da versão publicada nos dois destinos.
14
+
15
+ ## Contrato
16
+
17
+ - Manter npm público sem login e GitHub Packages com autenticação própria.
18
+ - Usar somente o artefato aprovado pelo CI do commit da tag.
19
+ - Conferir versão, integridade, visibilidade pública e associação ao repositório.
20
+ - Incluir os dois comandos de instalação no README, guias e notas da release.
21
+ - Preservar versões anteriores, arquivos humanos e configurações existentes.
22
+ - Guardar downloads e evidências em diretórios ignorados.
23
+
24
+ ## Critérios de aceite
25
+
26
+ | ID | Resultado observável |
27
+ | --- | --- |
28
+ | AC-001 | Versão, lockfile, changelog, guias e notas coerentes com 0.0.10. |
29
+ | AC-002 | CI aprovado nos quatro perfis e tag no commit verificado. |
30
+ | AC-003 | Versão 0.0.10 pública nos dois registries, com bytes iguais ao CI. |
31
+ | AC-004 | Versão, init, doctor e sync --check passam nos dois registries. |
32
+ | AC-005 | Licença e guias incluídos, sem artefatos locais ou credenciais. |
33
+
34
+ ## Validação
35
+
36
+ Testar geração das notas e distribuição local. Após o CI, publicar a tag,
37
+ baixar o artefato original e comparar os checksums da release e dos registries.
38
+ Usar caches e projetos novos: npm sem credenciais; GitHub com token exclusivo.
39
+ Conferir `latest`, README público, licença e os quatro comandos da CLI.
40
+
41
+ Os instaladores nativos dos sistemas são documentados com fontes oficiais;
42
+ esta release não afirma reinstalá-los em máquinas que já têm os pré-requisitos.
@@ -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.10",
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);