@pathbit/agentdevkit 0.0.0-stage → 0.0.5

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 (240) hide show
  1. package/.pbai4devs/COMPATIBILIDADE.md +109 -0
  2. package/.pbai4devs/INSTRUCTIONS.md +87 -0
  3. package/.pbai4devs/README.md +50 -0
  4. package/.pbai4devs/agents/README.md +18 -0
  5. package/.pbai4devs/agents/revisor/AGENT.md +16 -0
  6. package/.pbai4devs/agents/revisor/antigravity.yaml +8 -0
  7. package/.pbai4devs/agents/revisor/claude.yaml +5 -0
  8. package/.pbai4devs/agents/revisor/cursor.yaml +5 -0
  9. package/.pbai4devs/agents/revisor/gemini.yaml +6 -0
  10. package/.pbai4devs/agents/revisor/github.yaml +5 -0
  11. package/.pbai4devs/agents/revisor/kimi-code.yaml +5 -0
  12. package/.pbai4devs/agents/revisor/mimocode.yaml +10 -0
  13. package/.pbai4devs/agents/revisor/opencode.yaml +7 -0
  14. package/.pbai4devs/agents/revisor/zcode.yaml +5 -0
  15. package/.pbai4devs/commands/README.md +30 -0
  16. package/.pbai4devs/commands/atualizar-ferramentas/COMMAND.md +17 -0
  17. package/.pbai4devs/commands/diagnosticar/COMMAND.md +18 -0
  18. package/.pbai4devs/commands/instalar-ferramentas/COMMAND.md +21 -0
  19. package/.pbai4devs/commands/preparar-dev/COMMAND.md +14 -0
  20. package/.pbai4devs/commands/proximos-passos/COMMAND.md +9 -0
  21. package/.pbai4devs/commands/revisar-entrega/COMMAND.md +29 -0
  22. package/.pbai4devs/commands/sincronizar/COMMAND.md +20 -0
  23. package/.pbai4devs/commands/status-roadmap/COMMAND.md +12 -0
  24. package/.pbai4devs/commands/testar-navegador/COMMAND.md +16 -0
  25. package/.pbai4devs/commands/verificar-agentes/COMMAND.md +19 -0
  26. package/.pbai4devs/estado/README.md +12 -0
  27. package/.pbai4devs/external-skills/CATALOG.json +121 -0
  28. package/.pbai4devs/external-skills/README.md +48 -0
  29. package/.pbai4devs/rules/README.md +18 -0
  30. package/.pbai4devs/rules/ambiente-local/RULE.md +33 -0
  31. package/.pbai4devs/rules/ambiente-local/cursor.yaml +4 -0
  32. package/.pbai4devs/rules/documentacao/RULE.md +11 -0
  33. package/.pbai4devs/rules/documentacao/cursor.yaml +4 -0
  34. package/.pbai4devs/rules/estrutura/RULE.md +33 -0
  35. package/.pbai4devs/rules/estrutura/cursor.yaml +4 -0
  36. package/.pbai4devs/rules/git/RULE.md +18 -0
  37. package/.pbai4devs/rules/git/cursor.yaml +4 -0
  38. package/.pbai4devs/rules/proximos-passos/RULE.md +14 -0
  39. package/.pbai4devs/rules/qualidade/RULE.md +32 -0
  40. package/.pbai4devs/rules/qualidade/cursor.yaml +4 -0
  41. package/.pbai4devs/scripts/README.md +69 -0
  42. package/.pbai4devs/scripts/diagnosticar-ambiente/SCRIPT.mjs +41 -0
  43. package/.pbai4devs/scripts/diagnosticar-ambiente/tests/TEST.mjs +30 -0
  44. package/.pbai4devs/scripts/executar-script/README.md +28 -0
  45. package/.pbai4devs/scripts/executar-script/SCRIPT.mjs +78 -0
  46. package/.pbai4devs/scripts/executar-script/tests/TEST.mjs +122 -0
  47. package/.pbai4devs/scripts/gerenciar-skills-externas/README.md +31 -0
  48. package/.pbai4devs/scripts/gerenciar-skills-externas/SCRIPT.mjs +182 -0
  49. package/.pbai4devs/scripts/gerenciar-skills-externas/tests/TEST.mjs +134 -0
  50. package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +190 -0
  51. package/.pbai4devs/scripts/inspecionar-projeto/tests/TEST.mjs +161 -0
  52. package/.pbai4devs/scripts/lib/README.md +9 -0
  53. package/.pbai4devs/scripts/lib/adapters.mjs +135 -0
  54. package/.pbai4devs/scripts/lib/catalog.mjs +170 -0
  55. package/.pbai4devs/scripts/lib/gitignore.mjs +348 -0
  56. package/.pbai4devs/scripts/lib/npm.mjs +17 -0
  57. package/.pbai4devs/scripts/lib/resource-path.mjs +20 -0
  58. package/.pbai4devs/scripts/lib/runtime.mjs +28 -0
  59. package/.pbai4devs/scripts/lib/script-config.mjs +8 -0
  60. package/.pbai4devs/scripts/preparar-ferramentas/BOOTSTRAP.md +34 -0
  61. package/.pbai4devs/scripts/preparar-ferramentas/BOOTSTRAP.mjs +189 -0
  62. package/.pbai4devs/scripts/preparar-ferramentas/README.md +71 -0
  63. package/.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs +455 -0
  64. package/.pbai4devs/scripts/preparar-ferramentas/tests/FIXTURE.html +20 -0
  65. package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_BROWSER.mjs +55 -0
  66. package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_DESKTOP.mjs +61 -0
  67. package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_VERCEL.mjs +192 -0
  68. package/.pbai4devs/scripts/preparar-ferramentas/tests/TEST.mjs +552 -0
  69. package/.pbai4devs/scripts/preparar-ferramentas/tests/bootstrap.test.mjs +94 -0
  70. package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash/input.txt +2 -0
  71. package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash-overlay/input.txt +1 -0
  72. package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/skills/agentdevkit-smoke/SKILL.md +8 -0
  73. package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/webreel/index.html +6 -0
  74. package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/webreel/webreel.config.json +10 -0
  75. package/.pbai4devs/scripts/revisar-entrega/README.md +36 -0
  76. package/.pbai4devs/scripts/revisar-entrega/SCRIPT.mjs +249 -0
  77. package/.pbai4devs/scripts/revisar-entrega/tests/TEST.mjs +249 -0
  78. package/.pbai4devs/scripts/sincronizar-agentes/README.md +41 -0
  79. package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +473 -0
  80. package/.pbai4devs/scripts/sincronizar-agentes/tests/LOCAL_IGNORE.mjs +290 -0
  81. package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +869 -0
  82. package/.pbai4devs/scripts/testar-infraestrutura/SCRIPT.mjs +39 -0
  83. package/.pbai4devs/scripts/testar-infraestrutura/tests/TEST.mjs +29 -0
  84. package/.pbai4devs/scripts/testar-maturidade/README.md +54 -0
  85. package/.pbai4devs/scripts/testar-maturidade/SCRIPT.mjs +477 -0
  86. package/.pbai4devs/scripts/testar-maturidade/assets/calculator.mjs +1 -0
  87. package/.pbai4devs/scripts/testar-maturidade/assets/calculator.test.mjs.template +8 -0
  88. package/.pbai4devs/scripts/testar-maturidade/assets/http-api.test.mjs.template +28 -0
  89. package/.pbai4devs/scripts/testar-maturidade/assets/server.mjs +34 -0
  90. package/.pbai4devs/scripts/testar-maturidade/tests/TEST.mjs +305 -0
  91. package/.pbai4devs/scripts/testar-navegador/REFERENCE.md +49 -0
  92. package/.pbai4devs/scripts/testar-navegador/SCRIPT.conf +1 -0
  93. package/.pbai4devs/scripts/testar-navegador/SCRIPT.js +64 -0
  94. package/.pbai4devs/scripts/testar-navegador/tests/FAIL.js +8 -0
  95. package/.pbai4devs/scripts/testar-navegador/tests/SUCCESS.js +13 -0
  96. package/.pbai4devs/scripts/verificar-recursos/README.md +25 -0
  97. package/.pbai4devs/scripts/verificar-recursos/SCRIPT.mjs +209 -0
  98. package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +310 -0
  99. package/.pbai4devs/scripts/verificar-recursos/tests/ambiente-local.mjs +80 -0
  100. package/.pbai4devs/skills/README.md +24 -0
  101. package/.pbai4devs/skills/git/git-branch/SKILL.md +11 -0
  102. package/.pbai4devs/skills/git/git-commit/SKILL.md +16 -0
  103. package/.pbai4devs/skills/git/git-diff/SKILL.md +9 -0
  104. package/.pbai4devs/skills/git/git-fetch/SKILL.md +10 -0
  105. package/.pbai4devs/skills/git/git-log/SKILL.md +9 -0
  106. package/.pbai4devs/skills/git/git-merge/SKILL.md +13 -0
  107. package/.pbai4devs/skills/git/git-pull/SKILL.md +12 -0
  108. package/.pbai4devs/skills/git/git-push/SKILL.md +12 -0
  109. package/.pbai4devs/skills/git/git-status/SKILL.md +9 -0
  110. package/.pbai4devs/skills/git/git-worktree/SKILL.md +10 -0
  111. package/.pbai4devs/skills/produto/criar-design-doc/SKILL.md +37 -0
  112. package/.pbai4devs/skills/produto/criar-prd/SKILL.md +35 -0
  113. package/.pbai4devs/skills/produto/criar-ux-ui/SKILL.md +34 -0
  114. package/.pbai4devs/skills/projeto/atualizar-documentacao/SKILL.md +26 -0
  115. package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +62 -0
  116. package/.pbai4devs/skills/projeto/criar-agente/SKILL.md +20 -0
  117. package/.pbai4devs/skills/projeto/criar-comando/SKILL.md +25 -0
  118. package/.pbai4devs/skills/projeto/criar-recurso/SKILL.md +41 -0
  119. package/.pbai4devs/skills/projeto/criar-regra/SKILL.md +19 -0
  120. package/.pbai4devs/skills/projeto/criar-skill/SKILL.md +22 -0
  121. package/.pbai4devs/skills/projeto/executar-script/SKILL.md +29 -0
  122. package/.pbai4devs/skills/projeto/preparar-dev/SKILL.md +45 -0
  123. package/.pbai4devs/skills/projeto/preparar-ferramentas/SKILL.md +56 -0
  124. package/.pbai4devs/skills/projeto/proximos-passos/SKILL.md +23 -0
  125. package/.pbai4devs/skills/projeto/sincronizar-agentes/SKILL.md +42 -0
  126. package/.pbai4devs/skills/projeto/usar-skills-externas/SKILL.md +28 -0
  127. package/.pbai4devs/skills/projeto/verificar-trabalho/SKILL.md +45 -0
  128. package/.pbai4devs/skills/specs/criar-spec/SKILL.md +33 -0
  129. package/.pbai4devs/skills/specs/gerenciar-roadmap/SKILL.md +26 -0
  130. package/.pbai4devs/skills/specs/implementar-spec/SKILL.md +48 -0
  131. package/.pbai4devs/templates/README.md +65 -0
  132. package/.pbai4devs/templates/adr/TEMPLATE.md +21 -0
  133. package/.pbai4devs/templates/ambiente-local/Makefile.template +48 -0
  134. package/.pbai4devs/templates/ambiente-local/TEMPLATE.md +48 -0
  135. package/.pbai4devs/templates/ambiente-local/compose.local.yml.template +16 -0
  136. package/.pbai4devs/templates/ambiente-local/dockerignore.template +28 -0
  137. package/.pbai4devs/templates/changelog/TEMPLATE.md +17 -0
  138. package/.pbai4devs/templates/claude/TEMPLATE.md +8 -0
  139. package/.pbai4devs/templates/clients/ADAPTERS.json +306 -0
  140. package/.pbai4devs/templates/clients/TEMPLATE.md +12 -0
  141. package/.pbai4devs/templates/comando/TEMPLATE.md +15 -0
  142. package/.pbai4devs/templates/configuracao/TEMPLATE.md +46 -0
  143. package/.pbai4devs/templates/configuracao-raiz/TEMPLATE.md +22 -0
  144. package/.pbai4devs/templates/configuracao-raiz/editorconfig.template +21 -0
  145. package/.pbai4devs/templates/configuracao-raiz/env.example.template +3 -0
  146. package/.pbai4devs/templates/configuracao-raiz/gitattributes.template +6 -0
  147. package/.pbai4devs/templates/contribuicao/TEMPLATE.md +23 -0
  148. package/.pbai4devs/templates/design/TEMPLATE.md +30 -0
  149. package/.pbai4devs/templates/entrypoints/AGENTS.md +19 -0
  150. package/.pbai4devs/templates/entrypoints/CLAUDE.md +7 -0
  151. package/.pbai4devs/templates/entrypoints/GEMINI.md +7 -0
  152. package/.pbai4devs/templates/entrypoints/TEMPLATE.md +12 -0
  153. package/.pbai4devs/templates/gitignore/TEMPLATE.md +42 -0
  154. package/.pbai4devs/templates/gitignore/agents.ignore +45 -0
  155. package/.pbai4devs/templates/gitignore/clients.json +20 -0
  156. package/.pbai4devs/templates/gitignore/local.ignore +10 -0
  157. package/.pbai4devs/templates/gitignore/shared.ignore +60 -0
  158. package/.pbai4devs/templates/instrucoes-agentes/TEMPLATE.md +25 -0
  159. package/.pbai4devs/templates/plano-implementacao/TEMPLATE.md +27 -0
  160. package/.pbai4devs/templates/prd/TEMPLATE.md +30 -0
  161. package/.pbai4devs/templates/readme-pasta/TEMPLATE.md +16 -0
  162. package/.pbai4devs/templates/readme-projeto/TEMPLATE.md +34 -0
  163. package/.pbai4devs/templates/release/TEMPLATE.md +23 -0
  164. package/.pbai4devs/templates/revisao-entrega/TEMPLATE.md +41 -0
  165. package/.pbai4devs/templates/roadmap/TEMPLATE.md +18 -0
  166. package/.pbai4devs/templates/seguranca/TEMPLATE.md +22 -0
  167. package/.pbai4devs/templates/spec/TEMPLATE.md +61 -0
  168. package/.pbai4devs/templates/tasks/TEMPLATE.md +20 -0
  169. package/.pbai4devs/templates/ux-ui/TEMPLATE.md +26 -0
  170. package/.pbai4devs/tools/README.md +87 -0
  171. package/.pbai4devs/tools/agent-browser/README.md +26 -0
  172. package/.pbai4devs/tools/agent-browser/SCRIPT.mjs +5 -0
  173. package/.pbai4devs/tools/agent-browser/TOOL.json +33 -0
  174. package/.pbai4devs/tools/agent-desktop/README.md +26 -0
  175. package/.pbai4devs/tools/agent-desktop/SCRIPT.mjs +5 -0
  176. package/.pbai4devs/tools/agent-desktop/TOOL.json +30 -0
  177. package/.pbai4devs/tools/just-bash/README.md +23 -0
  178. package/.pbai4devs/tools/just-bash/SCRIPT.mjs +5 -0
  179. package/.pbai4devs/tools/just-bash/TOOL.json +27 -0
  180. package/.pbai4devs/tools/markdownlint-cli2/README.md +18 -0
  181. package/.pbai4devs/tools/markdownlint-cli2/SCRIPT.mjs +5 -0
  182. package/.pbai4devs/tools/markdownlint-cli2/TOOL.json +28 -0
  183. package/.pbai4devs/tools/opensrc/README.md +25 -0
  184. package/.pbai4devs/tools/opensrc/SCRIPT.mjs +5 -0
  185. package/.pbai4devs/tools/opensrc/TOOL.json +29 -0
  186. package/.pbai4devs/tools/portless/README.md +21 -0
  187. package/.pbai4devs/tools/portless/SCRIPT.mjs +5 -0
  188. package/.pbai4devs/tools/portless/TOOL.json +30 -0
  189. package/.pbai4devs/tools/skills/README.md +24 -0
  190. package/.pbai4devs/tools/skills/SCRIPT.mjs +5 -0
  191. package/.pbai4devs/tools/skills/TOOL.json +33 -0
  192. package/.pbai4devs/tools/webreel/README.md +23 -0
  193. package/.pbai4devs/tools/webreel/SCRIPT.mjs +5 -0
  194. package/.pbai4devs/tools/webreel/TOOL.json +25 -0
  195. package/CHANGELOG.md +58 -0
  196. package/CONTRIBUTING.md +73 -0
  197. package/LICENSE +21 -0
  198. package/README.en.md +78 -0
  199. package/README.es.md +80 -0
  200. package/README.md +88 -2
  201. package/bin/README.md +8 -0
  202. package/bin/agentdevkit.mjs +35 -0
  203. package/docs/README.md +81 -0
  204. package/docs/guides/README.md +14 -0
  205. package/docs/guides/cli.md +81 -0
  206. package/docs/guides/installation.md +78 -0
  207. package/docs/guides/resources.md +121 -0
  208. package/docs/guides/tools-and-testing.md +85 -0
  209. package/docs/guides/troubleshooting.md +151 -0
  210. package/docs/guides/workflow.md +130 -0
  211. package/docs/maintainers/README.md +12 -0
  212. package/docs/maintainers/community.md +49 -0
  213. package/docs/maintainers/development.md +54 -0
  214. package/docs/maintainers/maintenance.md +104 -0
  215. package/docs/maintainers/packages.md +76 -0
  216. package/docs/product/README.md +12 -0
  217. package/docs/product/design.md +94 -0
  218. package/docs/product/prd.md +64 -0
  219. package/docs/product/roadmap.md +36 -0
  220. package/docs/product/ux-ui.md +42 -0
  221. package/docs/specs/README.md +20 -0
  222. package/docs/specs/github-packages/SPEC.md +45 -0
  223. package/docs/specs/v0.0.1/README.md +10 -0
  224. package/docs/specs/v0.0.1/SPEC.md +57 -0
  225. package/docs/specs/v0.0.2/README.md +10 -0
  226. package/docs/specs/v0.0.2/SPEC.md +67 -0
  227. package/docs/specs/v0.0.3/README.md +10 -0
  228. package/docs/specs/v0.0.3/SPEC.md +64 -0
  229. package/docs/specs/v0.0.4/README.md +10 -0
  230. package/docs/specs/v0.0.4/SPEC.md +53 -0
  231. package/docs/specs/v0.0.5/README.md +9 -0
  232. package/docs/specs/v0.0.5/SPEC.md +42 -0
  233. package/package.json +52 -4
  234. package/src/README.md +16 -0
  235. package/src/adapters/index.mjs +1 -0
  236. package/src/cli.mjs +162 -0
  237. package/src/config.mjs +14 -0
  238. package/src/i18n.mjs +42 -0
  239. package/src/install.mjs +367 -0
  240. package/src/next.mjs +79 -0
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ // Inventario somente leitura; nomes e caminhos, nunca valores de configuracao.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { requireNode, isMain } from '../lib/runtime.mjs';
7
+
8
+ const repository = fs.realpathSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..'));
9
+ const defaultAgents = ['.agents', '.agent', '.gemini', '.claude', '.codex', '.cursor', '.github', '.opencode'];
10
+ const defaultExcluded = ['.git', '.pbai4devs', '.agent-tests', ...defaultAgents, '.vscode', 'node_modules', 'vendor', 'target', 'dist', 'build', 'bin', 'obj', '.venv', 'venv', '.cache', '.next', '.nuxt', 'coverage', 'test-results', 'playwright-report', 'blob-report', 'tmp', 'temp', 'examples'];
11
+ const manifests = new Map([
12
+ ['package.json', 'javascript'], ['deno.json', 'javascript'], ['deno.jsonc', 'javascript'],
13
+ ['pyproject.toml', 'python'], ['Pipfile', 'python'], ['setup.py', 'python'], ['setup.cfg', 'python'],
14
+ ['Cargo.toml', 'rust'], ['go.mod', 'go'], ['pom.xml', 'java'], ['build.gradle', 'java'], ['build.gradle.kts', 'kotlin'],
15
+ ['composer.json', 'php'], ['Gemfile', 'ruby'], ['mix.exs', 'elixir'], ['pubspec.yaml', 'dart'],
16
+ ['Package.swift', 'swift'], ['CMakeLists.txt', 'cpp']
17
+ ]);
18
+ const locks = new Set(['package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb', 'deno.lock', 'uv.lock', 'poetry.lock', 'Pipfile.lock', 'Cargo.lock', 'go.sum', 'composer.lock', 'Gemfile.lock', 'mix.lock', 'pubspec.lock', 'Package.resolved', 'packages.lock.json', 'gradle.lockfile']);
19
+ const requiredDirectories = ['.pbai4devs', '.vscode', 'src', 'tests', 'infra'];
20
+ function agentRoots(root) {
21
+ const file = path.join(root, '.pbai4devs/espelhos.conf');
22
+ if (!present(root, '.pbai4devs/espelhos.conf', false)) return defaultAgents;
23
+ const values = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(line => line.startsWith('agente|')).map(line => line.split('|')[1]);
24
+ const roots = [...new Set(values.filter(value => /^\.[a-z0-9][a-z0-9.-]*$/.test(value) && !['.git', '.pbai4devs', '.agent-tests'].includes(value)))];
25
+ return roots.length ? roots : defaultAgents;
26
+ }
27
+ const requiredFiles = ['.gitignore', '.gitattributes', '.editorconfig', '.dockerignore', 'Makefile', 'README.md'];
28
+ const localCompose = ['infra/compose/compose.local.yml', 'infra/compose/docker-compose.local.yml'];
29
+ const normalize = value => value.split(path.sep).join('/');
30
+ const commandName = value => /^[A-Za-z0-9_][A-Za-z0-9_:./-]{0,99}$/.test(value);
31
+ const present = (root, name, directory) => {
32
+ try {
33
+ let current = root; let item;
34
+ for (const part of name.split('/')) { current = path.join(current, part); item = fs.lstatSync(current); if (item.isSymbolicLink()) return false; }
35
+ return directory ? item.isDirectory() : item.isFile();
36
+ }
37
+ catch { return false; }
38
+ };
39
+ const inside = candidate => { const relative = path.relative(repository, candidate); return relative !== '..' && !relative.startsWith('..' + path.sep) && !path.isAbsolute(relative); };
40
+ const fixturePath = candidate => /^(?:\.agent-tests\/|tmp\/examples\/|examples\/generated\/)/.test(normalize(path.relative(repository, candidate)));
41
+
42
+ function metadata(root, file) {
43
+ if (!present(root, file, false) || fs.statSync(path.join(root, file)).size > 1024 * 1024) return { fields: {}, documents: {} };
44
+ const text = fs.readFileSync(path.join(root, file), 'utf8').replaceAll('\r', '');
45
+ const header = text.match(/^---\n([\s\S]*?)\n---(?:\n|$)/)?.[1];
46
+ const result = { fields: {}, documents: {} }; if (!header) return result;
47
+ let documents = false;
48
+ const scalar = raw => {
49
+ const value = raw.trim();
50
+ if (!value || ['null', '~'].includes(value)) return null;
51
+ if (value.startsWith('"')) { try { return JSON.parse(value); } catch { return null; } }
52
+ return value.startsWith("'") && value.endsWith("'") ? value.slice(1, -1).replaceAll("''", "'") : value;
53
+ };
54
+ for (const line of header.split('\n')) {
55
+ const top = line.match(/^([a-z_]+):\s*(.*)$/);
56
+ if (top) { documents = top[1] === 'documentos'; result.fields[top[1]] = scalar(top[2]); }
57
+ else if (documents) { const item = line.match(/^ ([a-z_]+):\s*(.*)$/); if (item) result.documents[item[1]] = scalar(item[2]); }
58
+ }
59
+ return result;
60
+ }
61
+
62
+ function maturity(root, report) {
63
+ const stateFile = '.pbai4devs/estado/configuracao/ESTADO.md'; const state = metadata(root, stateFile);
64
+ const safeDocument = value => typeof value === 'string' && !path.isAbsolute(value) && !value.includes('\\') && !value.split('/').some(part => !part || ['.', '..', '.git'].includes(part));
65
+ const planning = {};
66
+ for (const name of ['prd', 'ux_ui', 'design']) {
67
+ const file = state.documents[name];
68
+ const exists = safeDocument(file) && /\.md$/i.test(file) && present(root, file, false);
69
+ const status = exists ? metadata(root, file).fields.status : null;
70
+ planning[name] = { path: safeDocument(file) ? file : null, present: Boolean(exists), status: ['rascunho', 'aprovado', 'nao-aplicavel', 'pendente'].includes(status) ? status : null };
71
+ }
72
+ planning.ready = planning.prd.status === 'aprovado' && planning.design.status === 'aprovado' && ['aprovado', 'nao-aplicavel'].includes(planning.ux_ui.status);
73
+ let released = false; const releases = state.documents.releases;
74
+ if (safeDocument(releases) && present(root, releases, true)) {
75
+ const visit = (directory, depth = 0) => {
76
+ for (const entry of fs.readdirSync(path.join(root, directory), { withFileTypes: true }).slice(0, 100)) {
77
+ const file = normalize(path.join(directory, entry.name));
78
+ if (entry.isSymbolicLink()) continue;
79
+ if (entry.isDirectory() && depth < 2) visit(file, depth + 1);
80
+ else if (entry.isFile() && /^RELEASE\.md$/i.test(entry.name) && ['publicado', 'concluido', 'released'].includes(metadata(root, file).fields.status)) released = true;
81
+ }
82
+ };
83
+ visit(releases);
84
+ }
85
+ const stateValue = value => ['pendente', 'concluido'].includes(value) ? value : null;
86
+ const configuration = { present: present(root, stateFile, false), preparacao: stateValue(state.fields.preparacao), configuracao_tecnica: stateValue(state.fields.configuracao_tecnica) };
87
+ let level = 'preparacao';
88
+ if (Object.values(planning).some(item => item && typeof item === 'object' && item.present)) level = 'descoberta';
89
+ if (planning.ready) level = 'planejado';
90
+ if (report.mode === 'aplicacao-detectada') level = 'implementacao';
91
+ if (report.mode === 'aplicacao-detectada' && planning.ready && report.contract.complete && configuration.configuracao_tecnica === 'concluido') level = released ? 'manutencao' : 'configurado';
92
+ return { level, planning, configuration, released, signals: { application: report.mode === 'aplicacao-detectada', structure: report.contract.complete }, limits: ['Classificacao descritiva; status em arquivos nao substitui aprovacao, testes ou provas de funcionamento.'] };
93
+ }
94
+
95
+ export function inspectProject(root = repository, { check = false } = {}) {
96
+ requireNode();
97
+ root = fs.realpathSync(path.resolve(root));
98
+ if (!fs.statSync(root).isDirectory()) throw new Error('A origem deve ser um diretorio.');
99
+ const excluded = new Set(defaultExcluded);
100
+ const configuredAgents = agentRoots(root); configuredAgents.forEach(name => excluded.add(name));
101
+ const report = { mode: 'agentes-sem-aplicacao', languages: [], manifests: [], lockfiles: [], commands: [], compose: [], dockerfiles: [], sources: [], structure: { present: [], missing: [] }, scan: { maxDepth: 6, truncated: false }, warnings: [] };
102
+ const languages = new Set(); let entries = 0;
103
+ const warning = (file, reason) => report.warnings.push({ file, reason });
104
+ function commands(file, name) {
105
+ if (!['package.json', 'composer.json', 'deno.json', 'deno.jsonc', 'Makefile', 'makefile', 'GNUmakefile', 'pyproject.toml'].includes(name)) return;
106
+ if (fs.statSync(path.join(root, file)).size > 1024 * 1024) { warning(file, 'manifest-grande-demais'); return; }
107
+ const text = fs.readFileSync(path.join(root, file), 'utf8');
108
+ let names = [];
109
+ try {
110
+ if (['package.json', 'composer.json', 'deno.json', 'deno.jsonc'].includes(name)) {
111
+ const data = JSON.parse(text);
112
+ names = Object.keys((name.startsWith('deno.') ? data.tasks : data.scripts) || {});
113
+ } else if (name === 'pyproject.toml') {
114
+ let section = false;
115
+ for (const line of text.split(/\r?\n/)) {
116
+ if (/^\s*\[/.test(line)) section = /^\s*\[(?:project\.scripts|tool\.poetry\.scripts)\]\s*(?:#.*)?$/.test(line);
117
+ else if (section) { const match = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=/); if (match) names.push(match[1]); }
118
+ }
119
+ } else {
120
+ for (const line of text.split(/\r?\n/)) {
121
+ const match = line.match(/^([A-Za-z0-9_][A-Za-z0-9_ ./-]*)\s*:(?!=)/);
122
+ if (match) names.push(...match[1].trim().split(/\s+/));
123
+ }
124
+ }
125
+ names = [...new Set(names.filter(commandName))].sort();
126
+ if (names.length) report.commands.push({ file, names });
127
+ } catch { warning(file, 'manifest-nao-interpretado'); }
128
+ }
129
+ function walk(directory = '', depth = 0) {
130
+ const children = fs.readdirSync(path.join(root, directory), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
131
+ for (const item of children) {
132
+ if (++entries > 25000) { report.scan.truncated = true; return; }
133
+ const file = normalize(path.join(directory, item.name));
134
+ if (/^\.playwright/i.test(item.name)) continue;
135
+ if (item.isSymbolicLink()) continue;
136
+ if (item.isDirectory()) {
137
+ if (excluded.has(item.name) || excluded.has(item.name.toLowerCase()) || /^\.?playwright/i.test(item.name)) continue;
138
+ if (depth < report.scan.maxDepth) walk(file, depth + 1); else report.scan.truncated = true;
139
+ continue;
140
+ }
141
+ if (!item.isFile()) continue;
142
+ let language = manifests.get(item.name);
143
+ if (/\.(?:csproj|fsproj|vbproj|sln)$/.test(item.name)) language = 'dotnet';
144
+ if (/^requirements(?:[.-][A-Za-z0-9_-]+)?\.txt$/.test(item.name)) language = 'python';
145
+ if (language) { report.manifests.push(file); languages.add(language); }
146
+ if (locks.has(item.name)) report.lockfiles.push(file);
147
+ if (/^(?:docker-)?compose(?:\.[A-Za-z0-9_-]+)*\.ya?ml$/i.test(item.name)) report.compose.push(file);
148
+ if (/^Dockerfile(?:\.[A-Za-z0-9_-]+)?$/.test(item.name) || /\.Dockerfile$/.test(item.name)) report.dockerfiles.push(file);
149
+ if (/\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|cs|fs|vb|php|rb|ex|exs|dart|swift|c|cpp|cc|h|hpp|vue|svelte)$/.test(item.name) && !/(^|\/)(?:tests?|spec|fixtures)(\/|$)|\.(?:test|spec)\./.test(file) && report.sources.length < 12) report.sources.push(file);
150
+ commands(file, item.name);
151
+ }
152
+ }
153
+ walk();
154
+ for (const [names, directory] of [[[...requiredDirectories, ...configuredAgents], true], [requiredFiles, false]]) {
155
+ for (const name of names) report.structure[present(root, name, directory) ? 'present' : 'missing'].push(name);
156
+ }
157
+ const compose = localCompose.find(name => present(root, name, false));
158
+ report.structure[compose ? 'present' : 'missing'].push(compose || localCompose[0]);
159
+ if (report.manifests.length || report.lockfiles.length || report.sources.length) report.mode = 'aplicacao-detectada';
160
+ report.languages = [...languages].sort();
161
+ report.contract = { checked: check, complete: report.structure.missing.length === 0 };
162
+ report.maturity = maturity(root, report);
163
+ return report;
164
+ }
165
+
166
+ export function main(args = process.argv.slice(2)) {
167
+ requireNode();
168
+ let root = repository; let check = false; args = [...args];
169
+ while (args.length) {
170
+ const option = args.shift();
171
+ if (option === '--check') check = true;
172
+ else if (option === '--root' && args.length) {
173
+ const candidate = path.resolve(repository, args.shift());
174
+ if (!inside(candidate) || !fixturePath(candidate)) throw new Error('--root aceita somente fixtures em examples/generated/, .agent-tests/ ou tmp/examples/ do repositorio ativo.');
175
+ root = fs.realpathSync(candidate);
176
+ if (!inside(root) || !fixturePath(root)) throw new Error('A fixture deve permanecer em examples/generated/, .agent-tests/ ou tmp/examples/ do repositorio ativo.');
177
+ } else throw new Error('Opcao invalida; use [--check] [--root <fixture-no-repositorio>].');
178
+ }
179
+ const report = inspectProject(root, { check });
180
+ console.log(JSON.stringify(report));
181
+ return check && !report.contract.complete ? 1 : 0;
182
+ }
183
+
184
+ if (isMain(import.meta.url)) {
185
+ try { process.exitCode = main(); }
186
+ catch (error) {
187
+ console.error(JSON.stringify({ error: error.code ? `Falha de leitura (${error.code}).` : error.message }));
188
+ process.exitCode = 2;
189
+ }
190
+ }
@@ -0,0 +1,161 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import test from 'node:test';
6
+ import { projectRoot, requireNode } from '../../lib/runtime.mjs';
7
+
8
+ requireNode();
9
+ const script = join(projectRoot, '.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs');
10
+ function fixture(t) {
11
+ const home = join(projectRoot, '.agent-tests');
12
+ mkdirSync(home, { recursive: true });
13
+ const root = mkdtempSync(join(home, 'inspecionar-projeto-'));
14
+ t.after(() => rmSync(root, { recursive: true, force: true }));
15
+ const directory = name => mkdirSync(join(root, name), { recursive: true });
16
+ const file = (name, data = '') => { const target = join(root, name); mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, data); };
17
+ const run = (...args) => {
18
+ const result = spawnSync(process.execPath, [script, '--root', root, ...args], { encoding: 'utf8' });
19
+ assert.ifError(result.error);
20
+ return { ...result, report: result.stdout.trim() ? JSON.parse(result.stdout) : undefined };
21
+ };
22
+ return { root, directory, file, run };
23
+ }
24
+
25
+ test('identifica repositório sem aplicação e informa estrutura ausente', t => {
26
+ const { run } = fixture(t);
27
+ const result = run();
28
+ assert.equal(result.status, 0, result.stderr);
29
+ assert.equal(result.report.mode, 'agentes-sem-aplicacao');
30
+ assert.ok(result.report.structure.missing.includes('Makefile'));
31
+ assert.equal(run('--check').status, 1);
32
+ });
33
+
34
+ test('ignora agentes, dependências, estado e links simbólicos', t => {
35
+ const { root, file, run } = fixture(t);
36
+ file('.pbai4devs/tools/node_modules/example/package.json', '{"scripts":{"wrong":"secret"}}');
37
+ file('node_modules/example/package.json', '{}');
38
+ file('.git/package.json', '{}');
39
+ file('.agent-tests/package.json', '{}');
40
+ assert.equal(run().report.mode, 'agentes-sem-aplicacao');
41
+ try { symlinkSync(join(projectRoot, 'README.md'), join(root, 'linked.py')); }
42
+ catch (error) {
43
+ if (process.platform !== 'win32' || !['EPERM', 'EACCES'].includes(error.code)) throw error;
44
+ t.diagnostic('Link simbólico indisponível sem permissão do Windows.');
45
+ }
46
+ assert.equal(run().report.mode, 'agentes-sem-aplicacao');
47
+ });
48
+
49
+ test('descobre linguagens e comandos sem expor valores nem executar Python', t => {
50
+ const { root, file, run } = fixture(t);
51
+ const secret = 'THIS_VALUE_MUST_NEVER_APPEAR_393838';
52
+ file('.env', `TOKEN=${secret}`);
53
+ file('backend/pyproject.toml', `[project]\nname="existing"\n[project.scripts]\nserve="app:main"\n[tool.private]\npassword="${secret}"`);
54
+ file('backend/app.py', `password="${secret}"`);
55
+ file('deploy/docker-compose.dev.yml', `services:\n app:\n environment:\n TOKEN: ${secret}\n`);
56
+ file('Dockerfile', `ENV TOKEN=${secret}`);
57
+ file('Makefile', `dev:\n\t@echo ${secret}\ntest check:\n\t@echo ok\n`);
58
+ file('package.json', JSON.stringify({ scripts: { dev: `echo ${secret}`, test: 'exit 0' }, config: { token: secret } }));
59
+ const result = run();
60
+ assert.equal(result.status, 0, result.stderr);
61
+ assert.equal(result.report.mode, 'aplicacao-detectada');
62
+ assert.deepEqual(result.report.languages, ['javascript', 'python']);
63
+ assert.ok(result.report.compose.includes('deploy/docker-compose.dev.yml'));
64
+ assert.ok(result.report.structure.missing.includes('src'));
65
+ assert.deepEqual(result.report.commands.find(row => row.file === 'package.json').names, ['dev', 'test']);
66
+ assert.deepEqual(result.report.commands.find(row => row.file === 'Makefile').names, ['check', 'dev', 'test']);
67
+ assert.deepEqual(result.report.commands.find(row => row.file === 'backend/pyproject.toml').names, ['serve']);
68
+ assert.ok(!result.stdout.includes(secret));
69
+ assert.ok(!result.stderr.includes(secret));
70
+ assert.equal(readFileSync(join(root, '.env'), 'utf8'), `TOKEN=${secret}`);
71
+ });
72
+
73
+ test('aceita estrutura completa e raízes de agentes configuradas', t => {
74
+ const { directory, file, run } = fixture(t);
75
+ for (const name of ['.pbai4devs', '.agents', '.agent', '.gemini', '.claude', '.codex', '.cursor', '.github', '.opencode', '.vscode', 'src', 'tests', 'infra']) directory(name);
76
+ for (const name of ['.gitignore', '.gitattributes', '.editorconfig', '.dockerignore', 'README.md', 'Makefile']) file(name);
77
+ file('infra/compose/docker-compose.local.yml', 'services: {}\n');
78
+ const result = run('--check');
79
+ assert.equal(result.status, 0, result.stderr);
80
+ assert.equal(result.report.contract.complete, true);
81
+ assert.deepEqual(result.report.structure.missing, []);
82
+ file('.pbai4devs/espelhos.conf', 'agente|.github|plano\nagente|.dsh|plano\n');
83
+ file('.dsh/skills/tool/package.json', '{"scripts":{"hidden":"secret"}}');
84
+ const configured = run('--check');
85
+ assert.equal(configured.status, 0, configured.stderr);
86
+ assert.ok(configured.report.structure.present.includes('.dsh'));
87
+ assert.ok(!configured.report.manifests.some(name => name.startsWith('.dsh/')));
88
+ });
89
+
90
+ test('manifests inválidos geram aviso sem revelar seu conteúdo', t => {
91
+ const { file, run } = fixture(t);
92
+ const secret = 'MALFORMED_PRIVATE_VALUE';
93
+ file('package.json', '{ malformed ' + secret);
94
+ const result = run();
95
+ assert.equal(result.status, 0, result.stderr);
96
+ assert.ok(result.report.warnings.some(row => row.file === 'package.json'));
97
+ assert.ok(!result.stdout.includes(secret));
98
+ });
99
+
100
+ test('rejeita origens fora das fixtures e opções desconhecidas', () => {
101
+ for (const args of [['--root', dirname(projectRoot)], ['--root', '.pbai4devs'], ['--root', 'examples'], ['--root', 'examples/custom'], ['--unknown']]) {
102
+ const result = spawnSync(process.execPath, [script, ...args], { encoding: 'utf8' });
103
+ assert.ifError(result.error);
104
+ assert.equal(result.status, 2);
105
+ assert.equal(result.stdout, '');
106
+ assert.equal(typeof JSON.parse(result.stderr).error, 'string');
107
+ }
108
+ });
109
+
110
+ test('classifica sinais de maturidade sem confundir planejamento com produto funcionando', t => {
111
+ const { directory, file, run } = fixture(t);
112
+ const state = stage => file('.pbai4devs/estado/configuracao/ESTADO.md', `---\npreparacao: concluido\nconfiguracao_tecnica: ${stage}\ndocumentos:\n prd: docs/PRD.md\n ux_ui: docs/UX.md\n design: docs/DESIGN.md\n releases: docs/releases\n---\n# Estado\n`);
113
+ assert.equal(run().report.maturity.level, 'preparacao');
114
+ state('pendente'); file('docs/PRD.md', '---\nstatus: rascunho\n---\n# PRD\n');
115
+ assert.equal(run().report.maturity.level, 'descoberta');
116
+ for (const name of ['PRD', 'DESIGN']) file(`docs/${name}.md`, `---\nstatus: aprovado\n---\n# ${name}\n`);
117
+ file('docs/UX.md', '---\nstatus: nao-aplicavel\n---\n# UX\n');
118
+ assert.equal(run().report.maturity.level, 'planejado');
119
+ file('package.json', '{"scripts":{"test":"node --test"}}'); file('src/main.mjs', 'export const answer = 42;');
120
+ assert.equal(run().report.maturity.level, 'implementacao');
121
+ for (const name of ['.agents', '.agent', '.gemini', '.claude', '.codex', '.cursor', '.github', '.opencode', '.vscode', 'tests', 'infra']) directory(name);
122
+ for (const name of ['.gitignore', '.gitattributes', '.editorconfig', '.dockerignore', 'README.md', 'Makefile']) file(name);
123
+ file('infra/compose/compose.local.yml', 'services: {}\n'); state('concluido');
124
+ assert.equal(run('--check').report.maturity.level, 'configurado');
125
+ file('docs/releases/v1/RELEASE.md', '---\nstatus: publicado\n---\n# Release\n');
126
+ const result = run('--check'); assert.equal(result.status, 0);
127
+ assert.equal(result.report.maturity.level, 'manutencao');
128
+ assert.equal(result.report.maturity.released, true);
129
+ assert.match(result.report.maturity.limits[0], /nao substitui/);
130
+ });
131
+
132
+ test('exemplos e caches Playwright não viram aplicação do projeto real', t => {
133
+ const { file, run } = fixture(t);
134
+ for (const folder of ['examples/generated/a', 'examples/fixture', 'tmp/examples/a', 'TEMP/project', '.PLAYWRIGHT-MCP', 'playwright-results']) {
135
+ file(`${folder}/package.json`, '{}'); file(`${folder}/main.mjs`, 'export const fake=1;');
136
+ }
137
+ file('.PLAYWRIGHT-state.js', 'export const transient = true;');
138
+ const report = run().report;
139
+ assert.equal(report.mode, 'agentes-sem-aplicacao');
140
+ assert.deepEqual(report.manifests, []); assert.deepEqual(report.sources, []);
141
+ });
142
+
143
+ test('aceita inspecionar cenário em examples/generated sem liberar outras pastas', t => {
144
+ const base = join(projectRoot, 'examples/generated'); mkdirSync(base, { recursive: true });
145
+ const root = mkdtempSync(join(base, 'inspector-fixture-'));
146
+ t.after(() => rmSync(root, { recursive: true, force: true }));
147
+ writeFileSync(join(root, 'package.json'), '{}');
148
+ const result = spawnSync(process.execPath, [script, '--root', root], { encoding: 'utf8' });
149
+ assert.ifError(result.error); assert.equal(result.status, 0, result.stderr);
150
+ assert.equal(JSON.parse(result.stdout).mode, 'aplicacao-detectada');
151
+ });
152
+
153
+ test('status arbitrários e referências fora da raiz não expõem dados da configuração', t => {
154
+ const { file, run } = fixture(t); const secret = 'PRIVATE_STATUS_192819';
155
+ file('.pbai4devs/estado/configuracao/ESTADO.md', `---\npreparacao: ${secret}\nconfiguracao_tecnica: ${secret}\ndocumentos:\n prd: ../outside.md\n design: docs/DESIGN.md\n---\n# Estado\n`);
156
+ file('docs/DESIGN.md', `---\nstatus: ${secret}\n---\n# Design\n`);
157
+ const result = run(); assert.equal(result.status, 0);
158
+ assert.equal(result.report.maturity.planning.prd.path, null);
159
+ assert.equal(result.report.maturity.planning.ready, false);
160
+ assert.ok(!result.stdout.includes(secret));
161
+ });
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Utilitários mínimos compartilhados das automações Node.js.
3
+ ---
4
+
5
+ # Utilitários
6
+
7
+ `runtime.mjs` centraliza o requisito Node.js 24+, a raiz do repositório e a
8
+ identificação do arquivo executado. Não instala runtimes nem configura a máquina.
9
+ Também procura executáveis opcionais no PATH sem iniciar um shell.
@@ -0,0 +1,135 @@
1
+ import { lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { resourcePath } from './resource-path.mjs';
5
+ import { isGeneratedOutput } from './gitignore.mjs';
6
+
7
+ const policy = JSON.parse(readFileSync(new URL('../../templates/clients/ADAPTERS.json', import.meta.url), 'utf8'));
8
+ const kinds = new Set(['skills', 'agents', 'commands', 'rules']);
9
+ const layouts = new Set(['agrupado', 'plano', 'arquivo', 'mdc', 'agente', 'prompt', 'instrucoes']);
10
+ const entryNames = new Set(['AGENTS.md', 'CLAUDE.md', 'GEMINI.md']);
11
+ const safeRoot = value => typeof value === 'string' && /^\.[a-z0-9][a-z0-9-]*$/.test(value) && !['.git', '.pbai4devs', '.agent-tests'].includes(value);
12
+ const stringList = value => Array.isArray(value) && value.every(item => typeof item === 'string');
13
+ if (policy.schemaVersion !== 1 || !Array.isArray(policy.adapters) || !policy.adapters.length || !stringList(policy.entrypoints) || policy.entrypoints.some(name => !entryNames.has(name))) throw new Error('Politica de clientes invalida.');
14
+ const ids = new Set(), roots = new Set(), aliases = {};
15
+ for (const adapter of policy.adapters) {
16
+ if (!adapter || typeof adapter.id !== 'string' || !/^[a-z][a-z0-9-]*$/.test(adapter.id) || adapter.id === 'all' || ids.has(adapter.id) || typeof adapter.name !== 'string' || !safeRoot(adapter.root) || roots.has(adapter.root) || !adapter.formats || Object.entries(adapter.formats).some(([kind, layout]) => !kinds.has(kind) || !layouts.has(layout))) throw new Error('Adaptador invalido.');
17
+ if (/[\r\n]/.test(adapter.name) || typeof adapter.reason !== 'string' || /[\r\n]/.test(adapter.reason) || (adapter.discovery !== undefined && adapter.discovery !== 'antigravity')) throw new Error('Descricao ou descoberta de adaptador invalida.');
18
+ for (const field of ['aliases', 'requires', 'extraRoots', 'entrypoints', 'sources']) if (!stringList(adapter[field])) throw new Error('Lista invalida no adaptador: ' + field);
19
+ if (adapter.extraRoots.some(root => !safeRoot(root)) || adapter.entrypoints.some(name => !entryNames.has(name)) || adapter.sources.some(url => !url.startsWith('https://'))) throw new Error('Caminho ou fonte invalida no adaptador.');
20
+ for (const field of ['agentMetadata', 'ruleMetadata']) if (adapter[field] !== undefined && !/^[a-z0-9-]+\.ya?ml$/.test(adapter[field])) throw new Error('Metadados invalidos no adaptador.');
21
+ ids.add(adapter.id); roots.add(adapter.root);
22
+ }
23
+ for (const adapter of policy.adapters) {
24
+ if (adapter.requires.some(id => !ids.has(id))) throw new Error('Dependencia de adaptador desconhecida.');
25
+ for (const alias of adapter.aliases) {
26
+ if (!/^[a-z][a-z0-9-]*$/.test(alias) || ids.has(alias) || alias === 'all' || Object.hasOwn(aliases, alias)) throw new Error('Alias de adaptador invalido.');
27
+ aliases[alias] = adapter.id;
28
+ }
29
+ }
30
+ export const adapters = Object.freeze(policy.adapters.map(adapter => Object.freeze(adapter)));
31
+ export const availableClients = Object.freeze([
32
+ Object.freeze({ id: 'all', name: 'Todos os clientes', aliases: Object.freeze([]) }),
33
+ ...adapters.map(({ id, name, root, aliases }) => Object.freeze({ id, name, root, aliases })),
34
+ ]);
35
+ export const adapterRoots = Object.freeze([...new Set(adapters.flatMap(adapter => [adapter.root, ...adapter.extraRoots]))]);
36
+
37
+ export function normalizeClients(input) {
38
+ if (input === undefined) return ['all'];
39
+ const values = typeof input === 'string' ? [input] : input;
40
+ if (!Array.isArray(values) || !values.length || values.some(value => typeof value !== 'string')) throw new Error('Selecione ao menos um cliente valido.');
41
+ const selected = new Set();
42
+ for (const value of values) for (const part of value.split(',')) {
43
+ const requested = part.trim().toLowerCase(), id = aliases[requested] ?? requested;
44
+ if (!availableClients.some(client => client.id === id)) throw new Error('Cliente desconhecido: ' + (part || '(vazio)'));
45
+ selected.add(id);
46
+ }
47
+ if (selected.has('all')) {
48
+ if (selected.size !== 1) throw new Error('all nao pode ser combinado com outros clientes.');
49
+ return ['all'];
50
+ }
51
+ return adapters.filter(adapter => selected.has(adapter.id)).map(adapter => adapter.id);
52
+ }
53
+
54
+ export function resolveAdapters(input) {
55
+ const clients = normalizeClients(input), all = clients.includes('all');
56
+ const requested = all ? adapters.map(adapter => adapter.id) : clients;
57
+ const selected = new Set(requested);
58
+ for (const id of selected) for (const required of adapters.find(adapter => adapter.id === id).requires) selected.add(required);
59
+ const resolved = adapters.filter(adapter => selected.has(adapter.id));
60
+ const roots = [...new Set(resolved.flatMap(adapter => [adapter.root, ...adapter.extraRoots]))];
61
+ const selectedEntries = new Set([...policy.entrypoints, ...adapters.filter(adapter => requested.includes(adapter.id)).flatMap(adapter => adapter.entrypoints)]);
62
+ const entrypoints = [...entryNames].filter(name => selectedEntries.has(name));
63
+ return { clients, adapters: resolved, roots, entrypoints };
64
+ }
65
+ const principal = { skills: 'SKILL.md', agents: 'AGENT.md', commands: 'COMMAND.md', rules: 'RULE.md' };
66
+ const stat = path => { try { return lstatSync(path); } catch (error) { if (error.code === 'ENOENT') return null; throw error; } };
67
+
68
+ function checked(root, relative) {
69
+ let path = root;
70
+ for (const part of relative.split('/')) {
71
+ if (!part || part === '.' || part === '..') throw new Error(`Caminho de adaptador invalido: ${relative}`);
72
+ if (stat(path)?.isDirectory() && readdirSync(path).some(name => name.toLowerCase() === part.toLowerCase() && name !== part)) throw new Error(`Capitalizacao ambigua: ${relative}`);
73
+ path = join(path, part);
74
+ if (stat(path)?.isSymbolicLink()) throw new Error(`Link simbolico no adaptador: ${relative}`);
75
+ }
76
+ return path;
77
+ }
78
+
79
+ function sources(root, kind) {
80
+ const result = [];
81
+ function visit(relative) {
82
+ const path = checked(root, relative);
83
+ const item = stat(path);
84
+ if (!item) return;
85
+ if (!item.isDirectory()) throw new Error(`Diretorio de recursos invalido: ${relative}`);
86
+ for (const name of readdirSync(path).sort()) {
87
+ const child = `${relative}/${name}`, file = checked(root, child), info = stat(file);
88
+ if (isGeneratedOutput(child, info.isDirectory())) continue;
89
+ if (info.isDirectory()) visit(child);
90
+ else if (info.isFile() && name === principal[kind]) result.push(child);
91
+ }
92
+ }
93
+ visit(`.pbai4devs/${kind}`);
94
+ return result;
95
+ }
96
+
97
+ export function adapterManifest(root, { clients } = {}) {
98
+ const selection = resolveAdapters(clients);
99
+ root = realpathSync(resolve(root));
100
+ const rows = ['# Gerado por AgentDevKit; sincronizacao instalada nao depende do pacote npm.', '# tipo|cliente|recurso ou layout|formato ou metadados'];
101
+ const resources = Object.fromEntries(Object.keys(principal).map(kind => [kind, sources(root, kind)]));
102
+ for (const adapter of selection.adapters) {
103
+ rows.push(`# ${adapter.id}: ${adapter.reason}`, `agente|${adapter.root}|agrupado`);
104
+ for (const [kind, mode] of Object.entries(adapter.formats)) rows.push(`formato|${adapter.root}|${kind}|${mode}`);
105
+ if (adapter.discovery) rows.push(`descoberta|${adapter.root}|${adapter.discovery}`);
106
+ for (const [kind, key] of [['agents', 'agentMetadata'], ['rules', 'ruleMetadata']]) {
107
+ if (!adapter[key]) continue;
108
+ for (const source of resources[kind]) {
109
+ const metadata = `${source.slice(0, source.lastIndexOf('/'))}/${adapter[key]}`;
110
+ if (!stat(checked(root, metadata))?.isFile()) continue;
111
+ const target = `${adapter.root}/${resourcePath(source, kind, adapter.formats[kind] || 'agrupado')}`;
112
+ rows.push(`nativo|${target}|${source}|${metadata}`);
113
+ }
114
+ }
115
+ }
116
+ if (selection.roots.includes('.vscode')) rows.push('gitignore|.vscode');
117
+ return rows.join('\n') + '\n';
118
+ }
119
+
120
+ // Somente configura a politica. O chamador decide quando planejar/aplicar o sync.
121
+ export function writeAdapterManifest(root, { clients } = {}) {
122
+ const selection = resolveAdapters(clients);
123
+ root = realpathSync(resolve(root));
124
+ const relative = '.pbai4devs/espelhos.conf';
125
+ const file = checked(root, relative), content = adapterManifest(root, { clients: selection.clients });
126
+ const info = stat(file);
127
+ if (info && !info.isFile()) throw new Error('espelhos.conf deve ser arquivo regular.');
128
+ const result = { clients: selection.clients, roots: selection.roots, entrypoints: selection.entrypoints };
129
+ if (info && readFileSync(file, 'utf8') === content) return { changed: [], ...result };
130
+ mkdirSync(dirname(file), { recursive: true });
131
+ const temporary = `${file}.tmp-${randomUUID()}`;
132
+ try { writeFileSync(temporary, content, { flag: 'wx' }); renameSync(temporary, file); }
133
+ finally { rmSync(temporary, { force: true }); }
134
+ return { changed: [relative], ...result };
135
+ }