@pathbit/agentdevkit 0.0.0-stage → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/.pbai4devs/COMPATIBILIDADE.md +109 -0
  2. package/.pbai4devs/INSTRUCTIONS.md +87 -0
  3. package/.pbai4devs/README.md +53 -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 +65 -0
  196. package/CONTRIBUTING.md +73 -0
  197. package/LICENSE +21 -0
  198. package/README.en.md +89 -0
  199. package/README.es.md +91 -0
  200. package/README.md +94 -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 +86 -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 +87 -0
  216. package/docs/product/README.md +12 -0
  217. package/docs/product/design.md +104 -0
  218. package/docs/product/prd.md +70 -0
  219. package/docs/product/roadmap.md +38 -0
  220. package/docs/product/ux-ui.md +42 -0
  221. package/docs/specs/README.md +21 -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/docs/specs/v0.0.6/README.md +10 -0
  234. package/docs/specs/v0.0.6/SPEC.md +77 -0
  235. package/package.json +67 -4
  236. package/src/README.md +16 -0
  237. package/src/adapters/index.mjs +1 -0
  238. package/src/cli.mjs +162 -0
  239. package/src/config.mjs +14 -0
  240. package/src/i18n.mjs +42 -0
  241. package/src/install.mjs +373 -0
  242. package/src/next.mjs +79 -0
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { isMain, projectRoot, requireNode } from '../lib/runtime.mjs';
6
+ import { buildCatalog } from '../lib/catalog.mjs';
7
+ import { isGeneratedOutput } from '../lib/gitignore.mjs';
8
+
9
+ const fail = message => { throw new Error(message); };
10
+ const normalize = value => value.split(sep).join('/');
11
+ const excluded = new Set(['node_modules', '.git', 'opencode-loop', '.agent-tests']);
12
+ const toolState = new Set(['bin', '.cache', 'npm-cache']);
13
+ const auxiliary = ['templates', 'scripts', 'tools', 'external-skills', 'adapters', 'estado', 'tests', 'docs'];
14
+
15
+ function checkedStat(root, location) {
16
+ let item;
17
+ try { item = lstatSync(location); }
18
+ catch (error) { if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null; throw error; }
19
+ if (item.isSymbolicLink()) fail(`Link simbolico: ${normalize(relative(root, location))}`);
20
+ return item;
21
+ }
22
+
23
+ export function registryRoots(root) {
24
+ checkedStat(root, join(root, '.pbai4devs'));
25
+ const file = join(root, '.pbai4devs/espelhos.conf');
26
+ checkedStat(root, file);
27
+ const rows = readFileSync(file, 'utf8')
28
+ .split(/\r?\n/).filter(line => /^(?:agente|documento)\|/.test(line))
29
+ .map(line => line.split('|'));
30
+ return { agents: rows.filter(row => row[0] === 'agente').map(row => row[1]), paths: rows.map(row => row[1]) };
31
+ }
32
+
33
+ function walk(root, location, files, { skipTools = true } = {}) {
34
+ const path = normalize(relative(root, location));
35
+ if (/^examples\/generated(?:\/|$)/i.test(path)) return;
36
+ const item = checkedStat(root, location);
37
+ if (!item) return;
38
+ if (location === join(root, '.pbai4devs/external-skills/.local')) return;
39
+ if (skipTools && location === join(root, '.pbai4devs/tools')) return;
40
+ if (isGeneratedOutput(normalize(relative(root, location)), item.isDirectory())) return;
41
+ if (item.isDirectory()) {
42
+ if (excluded.has(basename(location))) return;
43
+ for (const child of readdirSync(location).sort()) walk(root, join(location, child), files, { skipTools });
44
+ } else if (item.isFile() && ['.md', '.mdc'].includes(extname(location).toLowerCase())) files.add(location);
45
+ }
46
+
47
+ export function collectMarkdown(root, registry = registryRoots(root)) {
48
+ const agents = join(root, '.pbai4devs');
49
+ checkedStat(root, agents);
50
+ for (const directory of readdirSync(agents, { withFileTypes: true })) {
51
+ const location = join(agents, directory.name);
52
+ const item = checkedStat(root, location);
53
+ if (isGeneratedOutput(normalize(relative(root, location)), item?.isDirectory())) continue;
54
+ if (item?.isDirectory() && !checkedStat(root, join(location, 'README.md'))?.isFile()) fail(`README ausente: .pbai4devs/${directory.name}/`);
55
+ }
56
+ const files = new Set();
57
+ const rootMarkdown = readdirSync(root).filter(name => ['.md', '.mdc'].includes(extname(name).toLowerCase()));
58
+ const sources = ['.pbai4devs', 'docs', 'src', 'bin', 'tests', 'scripts', 'examples',
59
+ '.github/PULL_REQUEST_TEMPLATE.md', '.github/ISSUE_TEMPLATE', ...rootMarkdown];
60
+ for (const name of [...sources, ...registry.paths]) walk(root, resolve(root, name), files);
61
+ const tools = join(agents, 'tools');
62
+ if (checkedStat(root, tools)) {
63
+ const guide = join(tools, 'README.md');
64
+ if (!checkedStat(root, guide)?.isFile()) fail('README ausente: .pbai4devs/tools/');
65
+ walk(root, guide, files);
66
+ for (const item of readdirSync(tools, { withFileTypes: true })) {
67
+ if (toolState.has(item.name) || item.name.startsWith('.')) continue;
68
+ if (!checkedStat(root, join(tools, item.name))?.isDirectory()) continue;
69
+ const file = join(tools, item.name, 'README.md');
70
+ const guide = checkedStat(root, file);
71
+ if (checkedStat(root, join(tools, item.name, 'TOOL.json')) && !guide?.isFile()) fail(`README ausente: .pbai4devs/tools/${item.name}/`);
72
+ if (guide) walk(root, file, files);
73
+ }
74
+ }
75
+ return [...files].sort();
76
+ }
77
+
78
+ export function checkFrontmatter(text, file) {
79
+ const lines = text.replaceAll('\r', '').split('\n');
80
+ const closing = lines.indexOf('---', 1);
81
+ const first = closing > 0 ? lines.slice(closing + 1).find(line => line.trim()) : undefined;
82
+ if (lines[0] !== '---' || closing < 1 || !first?.startsWith('# ')) fail(`Frontmatter/H1 invalido: ${file}`);
83
+ }
84
+
85
+ function withoutFences(text) {
86
+ let fence;
87
+ return text.split(/\r?\n/).map(line => {
88
+ const marker = line.match(/^\s{0,3}(`{3,}|~{3,})/);
89
+ if (marker) {
90
+ if (!fence) fence = marker[1];
91
+ else if (marker[1][0] === fence[0] && marker[1].length >= fence.length) fence = undefined;
92
+ return '';
93
+ }
94
+ return fence ? '' : line;
95
+ }).join('\n');
96
+ }
97
+
98
+ export function localLinkTargets(text) {
99
+ const content = withoutFences(text);
100
+ const targets = [...content.matchAll(/\]\((<[^>]*>|[^)]*)\)/g)].map(match => match[1]);
101
+ for (const match of content.matchAll(/^\s{0,3}\[[^\]^][^\]]*\]:\s*(<[^>]+>|\S+)/gm)) targets.push(match[1]);
102
+ return targets.map(value => {
103
+ const raw = value.trim();
104
+ const target = raw.startsWith('<') ? raw.slice(1, raw.indexOf('>')) : raw.split(/\s+["']/)[0];
105
+ if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target)) return null;
106
+ const file = target.split(/[?#]/)[0];
107
+ if (!file) return null;
108
+ try { return decodeURIComponent(file); }
109
+ catch { fail(`Link com codificacao invalida: ${target}`); }
110
+ }).filter(Boolean);
111
+ }
112
+
113
+ export function checkMarkdown(root, files, { virtualPaths = new Set() } = {}) {
114
+ for (const file of files) {
115
+ const label = normalize(relative(root, file));
116
+ const text = readFileSync(file, 'utf8');
117
+ checkFrontmatter(text, label);
118
+ for (const target of localLinkTargets(text)) {
119
+ const destination = resolve(dirname(file), target);
120
+ if (!existsSync(destination) && !virtualPaths.has(normalize(relative(root, destination)))) fail(`Link ausente: ${label} -> ${target}`);
121
+ }
122
+ }
123
+ }
124
+
125
+ export function resourcePaths(text) {
126
+ const result = new Set();
127
+ for (const match of text.matchAll(/(?<![\w/.-])\.pbai4devs\/(?:scripts|tools|commands|templates)\/[^\s`'"\[\])]+/g)) {
128
+ let path = match[0].replace(/[.,;:]+$/, '');
129
+ if (/[<>{}$*]/.test(path) || path.split('/').includes('...')) continue;
130
+ try { path = decodeURIComponent(path.split(/[?#]/)[0]); } catch { fail(`Referencia com codificacao invalida: ${path}`); }
131
+ if (path.split('/').some(part => ['.local', '.cache', 'node_modules'].includes(part)) || /^\.pbai4devs\/tools\/(?:bin|npm-cache)(?:\/|$)/.test(path)) continue;
132
+ result.add(path.replace(/\/$/, ''));
133
+ }
134
+ return [...result].sort();
135
+ }
136
+
137
+ export function checkResourceReferences(root, files) {
138
+ for (const file of files) for (const path of resourcePaths(readFileSync(file, 'utf8'))) {
139
+ const location = resolve(root, path); const child = relative(root, location);
140
+ if (isAbsolute(child) || child === '..' || child.startsWith(`..${sep}`) || path.split('/').some(part => part === '..')) fail(`Referencia fora do projeto: ${path}`);
141
+ let current = root;
142
+ for (const part of child.split(sep)) { current = join(current, part); if (!checkedStat(root, current)) fail(`Recurso ausente: ${normalize(relative(root, file))} -> ${path}`); }
143
+ if (/\.(?:mjs|cjs|js|md|mdc|json)$/.test(path) && !checkedStat(root, location).isFile()) fail(`Recurso deve ser arquivo: ${path}`);
144
+ }
145
+ }
146
+
147
+ export function checkCatalog(root, build = buildCatalog) {
148
+ const result = build(root);
149
+ for (const [path, expected] of result.outputs) {
150
+ const file = resolve(root, path); const child = relative(root, file);
151
+ if (isAbsolute(child) || child === '..' || child.startsWith(`..${sep}`)) fail(`Saida de catalogo fora do projeto: ${path}`);
152
+ if (!checkedStat(root, file)?.isFile() || readFileSync(file, 'utf8') !== expected) fail(`Catalogo divergente: ${path}; execute sincronizar-agentes.`);
153
+ }
154
+ return result.catalog;
155
+ }
156
+
157
+ export function checkAgentExtras(root, agents) {
158
+ for (const agent of agents) {
159
+ for (const name of auxiliary) if (existsSync(join(root, agent, name))) fail(`Auxiliar indevido em ${agent}/${name}`);
160
+ const files = new Set();
161
+ walk(root, join(root, agent), files);
162
+ if ([...files].some(file => ['README.md', 'INSTRUCTIONS.md'].includes(basename(file)))) fail(`Guia indevido em ${agent}`);
163
+ }
164
+ }
165
+
166
+ export function checkSynchronization(root, run = spawnSync) {
167
+ const result = run(process.execPath, [join(root, '.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs'), '--check'], { cwd: root, encoding: 'utf8', timeout: 60000 });
168
+ if (result.error) throw result.error;
169
+ if (result.status !== 0) fail(result.stderr?.trim() || result.stdout?.trim() || 'Espelhos divergentes.');
170
+ }
171
+
172
+ export function checkLint(root, files, run = spawnSync) {
173
+ const current = join(root, '.pbai4devs/tools/markdownlint-cli2/.local/CURRENT.json');
174
+ const legacy = join(root, '.pbai4devs/tools/markdownlint-cli2/CURRENT.json');
175
+ if (!existsSync(current) && !existsSync(legacy)) return ['markdownlint-cli2=missing; verificacao nativa de estrutura e links executada.'];
176
+ const script = join(root, '.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs');
177
+ const result = run(process.execPath, [script, 'run', 'markdownlint-cli2', ...files], { cwd: root, encoding: 'utf8', timeout: 60000 });
178
+ if (result.error) throw result.error;
179
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
180
+ if (result.status !== 0) fail(output.split('\n').filter(line => !line.startsWith('Finding:')).join('\n') || 'Markdown lint falhou.');
181
+ return output.split('\n').filter(line => /^(?:Linting:|Summary:)/.test(line));
182
+ }
183
+
184
+ export function verifyResources({ root = projectRoot, sourceOnly = false, synchronize = checkSynchronization, lint = checkLint, catalogCheck = checkCatalog } = {}) {
185
+ requireNode();
186
+ const built = sourceOnly ? buildCatalog(root) : null;
187
+ if (!sourceOnly) { synchronize(root); catalogCheck(root); }
188
+ const registry = sourceOnly ? { agents: [], paths: [] } : registryRoots(root);
189
+ const files = collectMarkdown(root, registry);
190
+ const virtualPaths = new Set(sourceOnly ? [...built.outputs.keys(), '.pbai4devs/espelhos.conf'] : []);
191
+ checkMarkdown(root, files, { virtualPaths });
192
+ checkResourceReferences(root, files);
193
+ checkAgentExtras(root, registry.agents);
194
+ return { files, messages: [...lint(root, files), 'recursos=passed'] };
195
+ }
196
+
197
+ export function main(args = process.argv.slice(2), options = {}) {
198
+ requireNode();
199
+ if (args.length === 1 && ['--help', '-h'].includes(args[0])) { console.log('Uso: node .pbai4devs/scripts/verificar-recursos/SCRIPT.mjs [--source-only]'); return 0; }
200
+ if (args.length && !(args.length === 1 && args[0] === '--source-only')) fail('Opcao invalida; use --help.');
201
+ if (args.includes('--source-only')) options = { ...options, sourceOnly: true };
202
+ for (const message of verifyResources(options).messages) console.log(message);
203
+ return 0;
204
+ }
205
+
206
+ if (isMain(import.meta.url)) {
207
+ try { process.exitCode = main(); }
208
+ catch (error) { console.error(error.message); process.exitCode = 1; }
209
+ }
@@ -0,0 +1,310 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { join, relative } 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
+ import { checkFrontmatter, checkLint, checkSynchronization, checkCatalog, checkResourceReferences, collectMarkdown, localLinkTargets, registryRoots, resourcePaths, verifyResources } from '../SCRIPT.mjs';
8
+ import { buildCatalog } from '../../lib/catalog.mjs';
9
+ import { platformKey, toolWrapper } from '../../preparar-ferramentas/SCRIPT.mjs';
10
+ import './ambiente-local.mjs';
11
+ import { mergeGitignore, planLocalIgnore, applyLocalIgnore } from '../../lib/gitignore.mjs';
12
+
13
+ requireNode();
14
+ const markdown = '# Exemplo\n';
15
+ const document = body => `---\ndescription: Fixture.\n---\n\n${body}`;
16
+ function fixture(t, prefix = 'verificar-recursos-') {
17
+ const home = join(projectRoot, '.agent-tests');
18
+ mkdirSync(home, { recursive: true });
19
+ const root = mkdtempSync(join(home, prefix));
20
+ t.after(() => rmSync(root, { recursive: true, force: true }));
21
+ const file = (name, content = document(markdown)) => {
22
+ const path = join(root, name);
23
+ mkdirSync(join(path, '..'), { recursive: true });
24
+ writeFileSync(path, content);
25
+ return path;
26
+ };
27
+ return { root, file };
28
+ }
29
+ function resourceFixture(t) {
30
+ const context = fixture(t);
31
+ context.file('.pbai4devs/espelhos.conf', 'agente|.codex|plano\n');
32
+ context.file('.pbai4devs/skills/README.md');
33
+ context.file('.pbai4devs/skills/exemplo/SKILL.md');
34
+ context.file('.pbai4devs/tools/README.md');
35
+ context.file('.pbai4devs/tools/demo/TOOL.json', JSON.stringify({ name: 'demo', description: 'CLI de prova.', group: 'base', source: 'https://github.com/owner/demo', download: { provider: 'npm', package: 'demo', registry: 'https://registry.npmjs.org/', repository: 'owner/demo', channel: 'latest' }, platforms: [platformKey()], scripts: { entry: 'SCRIPT.mjs' }, probe: ['--version'] }));
36
+ context.file('.pbai4devs/tools/demo/SCRIPT.mjs', toolWrapper('demo'));
37
+ context.file('.pbai4devs/tools/demo/README.md');
38
+ context.file('.codex/skills/exemplo/SKILL.md', document('# Exemplo\n\n[Origem](../../../.pbai4devs/skills/exemplo/SKILL.md)\n'));
39
+ return context;
40
+ }
41
+ const verify = root => verifyResources({ root, synchronize() {}, catalogCheck() {}, lint: () => ['lint=fixture'] });
42
+
43
+ function writeCatalog(root) {
44
+ for (const [path, content] of buildCatalog(root).outputs) { mkdirSync(join(root, path, '..'), { recursive: true }); writeFileSync(join(root, path), content); }
45
+ }
46
+
47
+ test('confere recursos sem percorrer downloads ou modificar a fixture', t => {
48
+ const { root, file } = resourceFixture(t);
49
+ file('.pbai4devs/tools/demo/.local/versions/package/README.md', 'invalido');
50
+ file('.pbai4devs/tools/bin/README.md', 'invalido');
51
+ file('.pbai4devs/tools/.cache/README.md', 'invalido');
52
+ file('.pbai4devs/tools/npm-cache/README.md', 'invalido');
53
+ const before = readdirSync(root, { recursive: true });
54
+ const result = verify(root);
55
+ assert.deepEqual(result.messages, ['lint=fixture', 'recursos=passed']);
56
+ assert.ok(result.files.includes(join(root, '.pbai4devs/tools/demo/README.md')));
57
+ assert.ok(!result.files.some(path => path.includes('.local')));
58
+ assert.deepEqual(readdirSync(root, { recursive: true }), before);
59
+ });
60
+
61
+ test('ignora resultados locais em qualquer caixa e ainda verifica fixtures comuns', t => {
62
+ const { root, file } = resourceFixture(t);
63
+ for (const path of ['.pbai4devs/TmP/invalid.md', '.pbai4devs/skills/exemplo/.PLAYWRIGHT-MCP/invalid.md', '.pbai4devs/skills/exemplo/playwright-report/invalid.md', '.pbai4devs/skills/exemplo/TEST-RESULTS/invalid.md', '.codex/skills/exemplo/.agent-tests/invalid.md']) file(path, '# Resultado sem frontmatter\n');
64
+ const kept = file('.pbai4devs/skills/exemplo/fixtures/cache/input.md');
65
+ assert.ok(verify(root).files.includes(kept));
66
+ file('.pbai4devs/skills/exemplo/fixtures/cache/input.md', '# Fixture invalida\n');
67
+ assert.throws(() => verify(root), /fixtures\/cache\/input.md/);
68
+ });
69
+
70
+ test('verifica Markdown de todas as pastas padrão e documentos da raiz', t => {
71
+ const { root, file } = resourceFixture(t);
72
+ const sources = ['src/README.md', 'bin/README.md', 'tests/README.md', 'scripts/release/README.md',
73
+ 'examples/README.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'GUIDE.MD',
74
+ '.github/PULL_REQUEST_TEMPLATE.md', '.github/ISSUE_TEMPLATE/question.md'];
75
+ for (const source of sources) file(source);
76
+ const collected = verify(root).files;
77
+ for (const source of sources) {
78
+ assert.ok(collected.includes(join(root, source)), source);
79
+ file(source, '# Sem frontmatter\n');
80
+ assert.throws(() => verify(root), /Frontmatter\/H1 invalido/);
81
+ file(source);
82
+ }
83
+ });
84
+
85
+ test('coleta source-only ignora exemplos gerados e clientes, sem exigir Git', t => {
86
+ const { root, file } = resourceFixture(t);
87
+ const guide = file('examples/README.md');
88
+ file('examples/generated/README.md', '# Resultado inválido\n');
89
+ file('examples/generated/demo/.pbai4devs/README.md', '# Cópia inválida\n');
90
+ file('src/node_modules/dependency/README.md', '# Download inválido\n');
91
+ file('scripts/TEST-RESULTS/README.md', '# Resultado inválido\n');
92
+ file('.github/skills/generated/SKILL.md', '# Cliente inválido\n');
93
+ file('.codex/skills/generated/SKILL.md', '# Cliente inválido\n');
94
+ const result = verifyResources({ root, sourceOnly: true, lint: () => ['lint=fixture'] });
95
+ assert.ok(result.files.includes(guide));
96
+ for (const path of result.files) assert.doesNotMatch(relative(root, path), /examples[\\/]generated|node_modules|TEST-RESULTS|\.github[\\/]skills|\.codex/);
97
+ });
98
+
99
+ test('catalogo derivado cobre recursos e detecta JSON editado sem reescrever', t => {
100
+ const { root, file } = resourceFixture(t);
101
+ file('.pbai4devs/commands/inspecionar/COMMAND.md', document('# Inspecionar\n\n[Script](../../scripts/inspecionar/SCRIPT.mjs).\n'));
102
+ file('.pbai4devs/scripts/README.md'); file('.pbai4devs/scripts/inspecionar/README.md');
103
+ file('.pbai4devs/scripts/inspecionar/SCRIPT.mjs', 'export {};\n');
104
+ file('.pbai4devs/templates/README.md'); file('.pbai4devs/templates/exemplo/TEMPLATE.md');
105
+ file('.pbai4devs/tools/demo/.local/versions/upstream/README.md', 'nao verificar');
106
+ writeCatalog(root);
107
+ const catalog = checkCatalog(root);
108
+ assert.equal(catalog.commands[0].id, 'inspecionar'); assert.equal(catalog.scripts[0].id, 'inspecionar');
109
+ assert.equal(catalog.templates[0].id, 'exemplo'); assert.equal(catalog.tools[0].name, 'demo');
110
+ assert.ok(!JSON.stringify(catalog).includes('upstream'));
111
+ const path = join(root, '.pbai4devs/catalog.json'); writeFileSync(path, '{"editado":true}\n');
112
+ assert.throws(() => checkCatalog(root), /Catalogo divergente/);
113
+ assert.equal(readFileSync(path, 'utf8'), '{"editado":true}\n');
114
+ writeCatalog(root);
115
+ const native = join(root, '.pbai4devs/skills.json'); writeFileSync(native, '{"entries":[]}\n');
116
+ assert.throws(() => checkCatalog(root), /skills.json/);
117
+ });
118
+
119
+ test('catalogo recusa TOOL incompleto, ponte ausente e referencia de comando quebrada', t => {
120
+ const { root, file } = resourceFixture(t); writeCatalog(root);
121
+ rmSync(join(root, '.pbai4devs/tools/demo/SCRIPT.mjs'));
122
+ assert.throws(() => checkCatalog(root), /Script ausente/);
123
+ file('.pbai4devs/tools/demo/SCRIPT.mjs', toolWrapper('demo'));
124
+ file('.pbai4devs/tools/demo/TOOL.json', '{}'); assert.throws(() => checkCatalog(root), /Nome/);
125
+ rmSync(join(root, '.pbai4devs/tools/demo'), { recursive: true });
126
+ file('.pbai4devs/commands/quebrado/COMMAND.md', document('# Quebrado\n\n[Script](../../scripts/ausente/SCRIPT.mjs).\n'));
127
+ assert.throws(() => checkCatalog(root), /Referencia ausente/);
128
+ });
129
+
130
+ test('caminhos concretos em comandos e exemplos apontam para arquivos reais', t => {
131
+ const { root, file } = resourceFixture(t);
132
+ file('.pbai4devs/scripts/tarefa/SCRIPT.mjs', 'export {};\n');
133
+ const command = file('.pbai4devs/commands/tarefa/COMMAND.md', document('# Tarefa\n\n```console\nnode .pbai4devs/scripts/tarefa/SCRIPT.mjs check\n```\n\nModelo: `.pbai4devs/templates/<nome>/TEMPLATE.md`.\n'));
134
+ checkResourceReferences(root, [command]);
135
+ assert.deepEqual(resourcePaths('`.pbai4devs/tools/demo/.local/CURRENT.json` e `.pbai4devs/tools/bin/demo.mjs`'), []);
136
+ assert.deepEqual(resourcePaths('`node .pbai4devs/scripts/tarefa/SCRIPT.mjs`'), ['.pbai4devs/scripts/tarefa/SCRIPT.mjs']);
137
+ rmSync(join(root, '.pbai4devs/scripts/tarefa/SCRIPT.mjs'));
138
+ assert.throws(() => checkResourceReferences(root, [command]), /Recurso ausente/);
139
+ });
140
+
141
+ test('external-skills mantém seu guia validado e só oculta o payload .local', t => {
142
+ const { root, file } = resourceFixture(t);
143
+ const guide = file('.pbai4devs/external-skills/README.md');
144
+ file('.pbai4devs/external-skills/CATALOG.json', '{"entries":[]}');
145
+ for (let index = 0; index < 80; index++) file(`.pbai4devs/external-skills/.local/upstream-${index}/SKILL.md`, '# Upstream sem frontmatter\n');
146
+ const result = verify(root);
147
+ assert.ok(result.files.includes(guide));
148
+ assert.ok(!result.files.some(path => /external-skills[\\/]\.local/.test(path)));
149
+ file('.pbai4devs/external-skills/README.md', '# Guia sem frontmatter\n');
150
+ assert.throws(() => verify(root), /Frontmatter\/H1 invalido: .pbai4devs\/external-skills\/README.md/);
151
+ file('.pbai4devs/external-skills/README.md');
152
+ file('.pbai4devs/external-skills/notes/guia.md', '# Documento externo ao payload\n');
153
+ assert.throws(() => verify(root), /Frontmatter\/H1 invalido: .pbai4devs\/external-skills\/notes\/guia.md/);
154
+ rmSync(join(root, '.pbai4devs/external-skills/notes'), { recursive: true });
155
+ file('.pbai4devs/skills/.local/guia.md', '# Este .local continua sendo verificado\n');
156
+ assert.throws(() => verify(root), /Frontmatter\/H1 invalido: .pbai4devs\/skills\/.local\/guia.md/);
157
+ });
158
+
159
+ test('external-skills é auxiliar proibido nas pastas dos agentes', t => {
160
+ const { root, file } = resourceFixture(t);
161
+ file('.codex/external-skills/CATALOG.json', '{"entries":[]}');
162
+ assert.throws(() => verify(root), /Auxiliar indevido em .codex\/external-skills/);
163
+ });
164
+
165
+ const symlinkCases = [
166
+ ['README.md', 'file', false],
167
+ ['.pbai4devs', 'dir', true],
168
+ ['.pbai4devs/espelhos.conf', 'file', true],
169
+ ['.pbai4devs/espelhos.conf', 'file', false],
170
+ ['.pbai4devs/tools', 'dir', true],
171
+ ['.pbai4devs/tools', 'dir', false],
172
+ ['.pbai4devs/tools/demo', 'dir', true],
173
+ ['.pbai4devs/tools/demo', 'dir', false],
174
+ ['.pbai4devs/tools/demo/README.md', 'file', false],
175
+ ['.pbai4devs/tools/demo/TOOL.json', 'file', false],
176
+ ['.pbai4devs/external-skills/.local', 'dir', true],
177
+ ['.pbai4devs/external-skills/.local', 'dir', false],
178
+ ];
179
+ for (const [name, type, targetExists] of symlinkCases) {
180
+ test(`rejeita link ${targetExists ? 'existente' : 'quebrado'} em ${name} antes de ler o destino`, t => {
181
+ const { root, file } = resourceFixture(t);
182
+ if (name.startsWith('.pbai4devs/external-skills/')) file('.pbai4devs/external-skills/README.md');
183
+ const target = join(root, 'payload', type === 'dir' ? 'directory' : 'file');
184
+ if (targetExists) {
185
+ if (type === 'dir') { file('payload/directory/README.md', 'payload invalido'); file('payload/directory/espelhos.conf', 'payload invalido'); }
186
+ else file('payload/file', 'payload invalido');
187
+ }
188
+ const link = join(root, name);
189
+ rmSync(link, { recursive: true, force: true });
190
+ try { symlinkSync(target, link, process.platform === 'win32' && type === 'dir' ? 'junction' : type); }
191
+ catch (error) {
192
+ if (process.platform === 'win32' && ['EPERM', 'EACCES'].includes(error.code)) { t.skip('Sem permissão para criar este link no Windows.'); return; }
193
+ throw error;
194
+ }
195
+ assert.throws(() => verify(root), error => error.message === `Link simbolico: ${name}`);
196
+ });
197
+ }
198
+
199
+ test('frontmatter e primeiro conteúdo H1 são obrigatórios', () => {
200
+ checkFrontmatter(document('# Correto\n'), 'ok.md');
201
+ checkFrontmatter(document('# Correto\r\n'), 'ok.mdc');
202
+ for (const text of ['# Sem YAML\n', '---\ndescription: aberto\n# H1\n', document('Texto\n# Tarde\n'), document('## H2\n')]) {
203
+ assert.throws(() => checkFrontmatter(text, 'erro.md'), /Frontmatter\/H1 invalido/);
204
+ }
205
+ });
206
+
207
+ test('valida links locais inline, referências e nomes codificados', t => {
208
+ const { root, file } = resourceFixture(t);
209
+ file('docs/target name.md');
210
+ const guide = file('README.md', document('# Links\n\n[Local](docs/target%20name.md#titulo)\n[Outro][target]\n[Web](https://example.com)\n[Email](mailto:teste@example.com)\n[Âncora](#titulo)\n\n[target]: <docs/target name.md>\n\n```md\n[Exemplo](nao-existe.md)\n```\n'));
211
+ verify(root);
212
+ file('README.md', document('# Falha\n\n[Local](missing.md)\n'));
213
+ assert.throws(() => verify(root), /Link ausente: README.md -> missing.md/);
214
+ writeFileSync(guide, document('# Falha\n\n[Local][ref]\n\n[ref]: missing.md\n'));
215
+ assert.throws(() => verify(root), /Link ausente/);
216
+ assert.throws(() => localLinkTargets('[Local](invalid%Q0.md)'), /codificacao invalida/);
217
+ });
218
+
219
+ test('inclui mdc e documentos registrados no catálogo', t => {
220
+ const { root, file } = resourceFixture(t);
221
+ file('.pbai4devs/espelhos.conf', 'agente|.codex|plano\ndocumento|EXTRA.md|AGENTS.md\n');
222
+ file('EXTRA.md');
223
+ file('.codex/rules/exemplo.mdc', document('## H2 invalido\n'));
224
+ assert.throws(() => verify(root), /Frontmatter\/H1 invalido: .codex\/rules\/exemplo.mdc/);
225
+ file('.codex/rules/exemplo.mdc');
226
+ assert.ok(collectMarkdown(root).includes(join(root, 'EXTRA.md')));
227
+ });
228
+
229
+ test('exige guias da origem e impede auxiliares nos espelhos', t => {
230
+ const { root, file } = resourceFixture(t);
231
+ rmSync(join(root, '.pbai4devs/tools/demo/README.md'));
232
+ assert.throws(() => verify(root), /README ausente: .pbai4devs\/tools\/demo/);
233
+ file('.pbai4devs/tools/demo/README.md');
234
+ file('.codex/README.md');
235
+ assert.throws(() => verify(root), /Guia indevido em .codex/);
236
+ rmSync(join(root, '.codex/README.md'));
237
+ file('.codex/tools/tool.json', '{}');
238
+ assert.throws(() => verify(root), /Auxiliar indevido em .codex\/tools/);
239
+ });
240
+
241
+ test('chama sincronizador e linter pelo Node atual, sem wrappers shell', t => {
242
+ const { root, file } = resourceFixture(t);
243
+ const calls = [];
244
+ const run = (...args) => { calls.push(args); return { status: 0, stdout: 'Finding: fixture\nLinting: 2 files\nSummary: 0 error(s)\n', stderr: '' }; };
245
+ checkSynchronization(root, run);
246
+ assert.equal(calls[0][0], process.execPath);
247
+ assert.deepEqual(calls[0][1], [join(root, '.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs'), '--check']);
248
+ assert.match(checkLint(root, [], run)[0], /markdownlint-cli2=missing/);
249
+ file('.pbai4devs/tools/markdownlint-cli2/.local/CURRENT.json', '{}');
250
+ const files = [join(root, 'README.md')];
251
+ assert.deepEqual(checkLint(root, files, run), ['Linting: 2 files', 'Summary: 0 error(s)']);
252
+ assert.equal(calls[1][0], process.execPath);
253
+ assert.deepEqual(calls[1][1], [join(root, '.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs'), 'run', 'markdownlint-cli2', ...files]);
254
+ assert.throws(() => checkSynchronization(root, () => ({ status: 1, stderr: 'espelhos divergentes' })), /espelhos divergentes/);
255
+ assert.throws(() => checkLint(root, files, () => ({ status: 1, stdout: 'Finding: fixture\nREADME.md: MD041' })), /README.md: MD041/);
256
+ });
257
+
258
+
259
+ function git(root, args, input) {
260
+ const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8', input });
261
+ if (result.error) throw result.error;
262
+ return result;
263
+ }
264
+ function ignoredPaths(root, paths) {
265
+ const result = git(root, ['check-ignore', '--no-index', '--stdin', '-z'], paths.join('\0') + '\0');
266
+ assert.ok([0, 1].includes(result.status), result.stderr);
267
+ return result.stdout.split('\0').filter(Boolean);
268
+ }
269
+ function ignoreFixture(t) {
270
+ const context = fixture(t, 'local-source-ignore-');
271
+ assert.equal(git(context.root, ['init', '-q']).status, 0);
272
+ for (const name of ['local.ignore', 'shared.ignore', 'agents.ignore']) context.file('.pbai4devs/templates/gitignore/' + name, readFileSync(join(projectRoot, '.pbai4devs/templates/gitignore/' + name), 'utf8'));
273
+ context.file('.pbai4devs/.gitignore', mergeGitignore('', readFileSync(join(projectRoot, '.pbai4devs/templates/gitignore/shared.ignore'), 'utf8')).text);
274
+ for (const root of ['.github', '.vscode']) context.file(root + '/.gitignore', mergeGitignore('', readFileSync(join(projectRoot, '.pbai4devs/templates/gitignore/agents.ignore'), 'utf8')).text);
275
+ return context;
276
+ }
277
+
278
+ test('fontes novas permanecem elegíveis e derivados locais ficam fora do índice', t => {
279
+ const { root, file } = ignoreFixture(t);
280
+ const outputs = ['.github/skills/generated/SKILL.md', '.vscode/.gitignore', '.github/.gitignore'];
281
+ applyLocalIgnore(planLocalIgnore(root, { outputs }));
282
+ const sources = ['src/new.mjs', 'bin/new.mjs', 'tests/new.test.mjs', 'scripts/release/new.mjs', '.github/workflows/own.yml', '.github/ISSUE_TEMPLATE/bug.yml', '.github/skills/human/SKILL.md', '.vscode/settings.json'];
283
+ const generated = [...outputs, '.pbai4devs/catalog.json', '.pbai4devs/skills.json', '.pbai4devs/espelhos.conf', '.pbai4devs/estado/agentdevkit.json'];
284
+ for (const path of [...sources, ...generated]) file(path, 'fixture\n');
285
+ assert.deepEqual(ignoredPaths(root, sources), []);
286
+ assert.deepEqual(new Set(ignoredPaths(root, generated)), new Set(generated));
287
+ assert.equal(git(root, ['add', '--all']).status, 0);
288
+ const tracked = git(root, ['ls-files', '-z']).stdout.split('\0');
289
+ for (const path of sources) assert.ok(tracked.includes(path), path);
290
+ for (const path of generated) assert.equal(tracked.includes(path), false, path);
291
+ });
292
+
293
+ test('ignores locais cobrem resultados em qualquer caixa sem bloquear fixtures', t => {
294
+ const { root } = ignoreFixture(t);
295
+ const ignored = ['.pbai4devs/.PLAYWRIGHT-MCP/session.json', '.pbai4devs/tests/TestResults/report.xml', '.github/skills/demo/.env', '.github/skills/demo/NoDe_MoDuLeS/dep.js', '.vscode/logs/session.log'];
296
+ assert.deepEqual(new Set(ignoredPaths(root, ignored)), new Set(ignored));
297
+ assert.deepEqual(ignoredPaths(root, ['.pbai4devs/tests/fixtures/cache/input.json', '.github/skills/demo/.env.development.example', '.vscode/new-config.json']), []);
298
+ });
299
+
300
+ test('validação source-only não exige catálogo, manifesto ou adaptadores gerados', t => {
301
+ const { root, file } = resourceFixture(t);
302
+ rmSync(join(root, '.pbai4devs/espelhos.conf'));
303
+ file('.pbai4devs/README.md', document('# Origem\n\n[Índice](catalog.json)\n'));
304
+ const before = readdirSync(root, { recursive: true }).sort();
305
+ const result = verifyResources({ root, sourceOnly: true, synchronize() { throw new Error('Não sincronizar fontes'); }, lint: () => ['lint=fixture'] });
306
+ assert.ok(result.files.includes(join(root, '.pbai4devs/README.md')));
307
+ assert.deepEqual(readdirSync(root, { recursive: true }).sort(), before);
308
+ file('.pbai4devs/README.md', document('# Origem\n\n[Erro](not-a-generated-file.json)\n'));
309
+ assert.throws(() => verifyResources({ root, sourceOnly: true, lint: () => [] }), /Link ausente/);
310
+ });
@@ -0,0 +1,80 @@
1
+ import assert from 'node:assert/strict';
2
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import test from 'node:test';
6
+ import { projectRoot } from '../../lib/runtime.mjs';
7
+
8
+ const templates = join(projectRoot, '.pbai4devs/templates/ambiente-local');
9
+ function fixture(t) {
10
+ const home = join(projectRoot, '.agent-tests');
11
+ mkdirSync(home, { recursive: true });
12
+ const root = mkdtempSync(join(home, 'ambiente-local-'));
13
+ t.after(() => rmSync(root, { recursive: true, force: true }));
14
+ copyFileSync(join(templates, 'Makefile.template'), join(root, 'Makefile'));
15
+ copyFileSync(join(templates, 'compose.local.yml.template'), join(root, 'compose.local.yml'));
16
+ return root;
17
+ }
18
+
19
+ test('modelos exigem comandos reais e configuração local explícita', () => {
20
+ const makefile = readFileSync(join(templates, 'Makefile.template'), 'utf8');
21
+ const setup = makefile.slice(makefile.indexOf('\nsetup:'), makefile.indexOf('\nup:'));
22
+ assert.ok(setup.indexOf('$(SETUP_COMMAND)') < setup.indexOf('$(DOCTOR_COMMANDS)'));
23
+ for (const command of ['SETUP_COMMAND', 'BUILD_COMMAND', 'TEST_COMMAND', 'LINT_COMMAND']) {
24
+ assert.ok(makefile.includes(`$(if $(strip $(${command})),,$(error Defina ${command}`));
25
+ }
26
+ assert.match(makefile, /docker version\r?\ndocker compose version\r?\n\$\(COMPOSE\) config --quiet/);
27
+ const compose = readFileSync(join(templates, 'compose.local.yml.template'), 'utf8');
28
+ for (const name of ['COMPOSE_PROJECT_NAME', 'APP_IMAGE', 'APP_PORT', 'APP_CONTAINER_PORT', 'APP_HEALTHCHECK']) assert.ok(compose.includes(`\${${name}:?`));
29
+ assert.ok(compose.includes('127.0.0.1:'));
30
+ assert.ok(compose.includes('healthcheck:'));
31
+ });
32
+
33
+ test('Makefile prepara o ambiente antes de validar e propaga falhas', t => {
34
+ if (process.platform === 'win32') { t.skip('Fixture de Make opcional para POSIX; contrato validado em Node em todos os sistemas.'); return; }
35
+ const available = spawnSync('make', ['--version'], { encoding: 'utf8' });
36
+ if (available.error || available.status !== 0) { t.skip('make ausente; contrato do modelo validado em Node.'); return; }
37
+ const root = fixture(t);
38
+ const log = join(root, 'order.log');
39
+ const quote = value => `'${value.replaceAll("'", "'\\''")}'`;
40
+ const node = quote(process.execPath);
41
+ const stub = join(root, 'docker-stub.mjs');
42
+ const setup = join(root, 'setup.mjs');
43
+ const failure = join(root, 'failure.mjs');
44
+ writeFileSync(stub, `import { appendFileSync, existsSync } from 'node:fs';\nconst command = process.argv.slice(2).join(' ');\nappendFileSync(process.env.AGENT_TEMPLATE_ORDER, command + '\\n');\nif (!['version', 'compose version', 'compose -f compose.local.yml config --quiet'].includes(command)) process.exit(99);\nif (command.endsWith('config --quiet') && !existsSync('.env')) process.exit(1);\n`);
45
+ writeFileSync(setup, `import { appendFileSync, copyFileSync } from 'node:fs';\nappendFileSync(process.env.AGENT_TEMPLATE_ORDER, 'setup\\n');\ncopyFileSync('.env.example', '.env');\n`);
46
+ writeFileSync(failure, 'process.exit(7);\n');
47
+ writeFileSync(join(root, '.env.example'), 'FIXTURE_ENV=ready\n');
48
+ writeFileSync(join(root, 'fixture.mk'), `include Makefile\ndefine DOCTOR_COMMANDS\n${node} ${quote(stub)} version\n${node} ${quote(stub)} compose version\n${node} ${quote(stub)} compose -f compose.local.yml config --quiet\nendef\n`);
49
+ const run = args => spawnSync('make', ['-f', 'fixture.mk', ...args], { cwd: root, encoding: 'utf8', env: { ...process.env, AGENT_TEMPLATE_ORDER: log } });
50
+ const result = run(['setup', `SETUP_COMMAND=${node} ${quote(setup)}`]);
51
+ assert.equal(result.status, 0, result.stderr || result.stdout);
52
+ assert.ok(existsSync(join(root, '.env')));
53
+ assert.deepEqual(readFileSync(log, 'utf8').trim().split('\n'), ['setup', 'version', 'compose version', 'compose -f compose.local.yml config --quiet']);
54
+ for (const [target, variable] of [['setup', 'SETUP_COMMAND'], ['build', 'BUILD_COMMAND'], ['test', 'TEST_COMMAND'], ['lint', 'LINT_COMMAND']]) {
55
+ writeFileSync(log, '');
56
+ const missing = run([target]);
57
+ assert.notEqual(missing.status, 0);
58
+ assert.match(missing.stderr, new RegExp(variable));
59
+ assert.equal(readFileSync(log, 'utf8'), '');
60
+ const failed = run([target, `${variable}=${node} ${quote(failure)}`]);
61
+ assert.notEqual(failed.status, 0);
62
+ assert.equal(readFileSync(log, 'utf8'), '');
63
+ }
64
+ });
65
+
66
+ test('Compose aceita valores explícitos e rejeita configuração incompleta', t => {
67
+ const available = spawnSync('docker', ['compose', 'version'], { encoding: 'utf8' });
68
+ if (available.error || available.status !== 0) { t.skip('Docker Compose CLI ausente; nenhum daemon é necessário.'); return; }
69
+ const root = fixture(t);
70
+ const empty = join(root, 'empty.env');
71
+ writeFileSync(empty, '');
72
+ const env = { ...process.env };
73
+ for (const name of ['COMPOSE_PROJECT_NAME', 'APP_IMAGE', 'APP_PORT', 'APP_CONTAINER_PORT', 'APP_HEALTHCHECK']) delete env[name];
74
+ const args = ['compose', '--env-file', empty, '-f', join(root, 'compose.local.yml'), 'config', '--quiet'];
75
+ const valid = spawnSync('docker', args, { cwd: root, encoding: 'utf8', env: { ...env, COMPOSE_PROJECT_NAME: 'agents-template-check', APP_IMAGE: 'example/app:1.0', APP_PORT: '39001', APP_CONTAINER_PORT: '3000', APP_HEALTHCHECK: 'test -f /tmp/ready' } });
76
+ assert.equal(valid.status, 0, valid.stderr);
77
+ const missing = spawnSync('docker', args, { cwd: root, encoding: 'utf8', env });
78
+ assert.notEqual(missing.status, 0);
79
+ assert.match(missing.stderr, /COMPOSE_PROJECT_NAME|APP_IMAGE|APP_PORT|APP_CONTAINER_PORT|APP_HEALTHCHECK/);
80
+ });
@@ -0,0 +1,24 @@
1
+ ---
2
+ description: Finalidade e criação de skills compartilhadas.
3
+ ---
4
+
5
+ # Skills
6
+
7
+ São procedimentos reutilizáveis, organizados em `git`, `projeto`, `produto` e
8
+ `specs`. Cada skill usa `<grupo>/<nome>/SKILL.md` com nome e descrição no YAML.
9
+
10
+ - `git`: operações de versionamento.
11
+ - `projeto`: configuração, criação e manutenção dos recursos.
12
+ - `produto`: PRD, experiência e design técnico.
13
+ - `specs`: especificações, roadmap e implementação.
14
+
15
+ Use `criar-skill` quando uma tarefa recorrente precisar de decisões próprias;
16
+ use `criar-recurso` para encontrar um fluxo existente antes de criar outro.
17
+ Mantenha instruções essenciais no arquivo e detalhes opcionais em referências.
18
+ Use `usar-skills-externas` para referências curadas, `atualizar-documentacao`
19
+ para manter os documentos e `revisar-entrega` para concluir implementações.
20
+ Use [proximos-passos](projeto/proximos-passos/SKILL.md) para orientar a
21
+ continuidade conforme a fase e as pendências reais.
22
+
23
+ O [guia compartilhado](../INSTRUCTIONS.md) orienta o uso das skills.
24
+ Ao alterar o inventário, sincronize os índices e apontamentos locais.
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: git-branch
3
+ description: Cria, inspeciona ou troca branches Git.
4
+ ---
5
+
6
+ # git-branch
7
+
8
+ Confira branch atual, worktrees e alterações locais. Crie ou troque a branch
9
+ solicitada sem sobrescrever ou descartar trabalho; siga o fluxo de specs em
10
+ [implementar-spec](../../specs/implementar-spec/SKILL.md), incluindo a escolha
11
+ da branch de base conforme o projeto existente.