@seedgrid/ai-toolkit 0.0.0-stage → 0.3.1

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 (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -2
  3. package/dist/adapters/claude.js +114 -0
  4. package/dist/adapters/codex.js +108 -0
  5. package/dist/adapters/index.js +17 -0
  6. package/dist/adapters/shared.js +142 -0
  7. package/dist/adapters/types.js +1 -0
  8. package/dist/cli.js +209 -0
  9. package/dist/commands/common.js +98 -0
  10. package/dist/commands/doctor.js +290 -0
  11. package/dist/commands/init.js +9 -0
  12. package/dist/commands/install.js +107 -0
  13. package/dist/commands/remove.js +54 -0
  14. package/dist/commands/review.js +127 -0
  15. package/dist/commands/selfinstall.js +63 -0
  16. package/dist/commands/status.js +73 -0
  17. package/dist/commands/update.js +8 -0
  18. package/dist/commands/validate.js +27 -0
  19. package/dist/core/apply.js +181 -0
  20. package/dist/core/blocks.js +64 -0
  21. package/dist/core/content.js +528 -0
  22. package/dist/core/env.js +40 -0
  23. package/dist/core/frontmatter.js +29 -0
  24. package/dist/core/hash.js +14 -0
  25. package/dist/core/inspect.js +51 -0
  26. package/dist/core/lock.js +124 -0
  27. package/dist/core/paths.js +65 -0
  28. package/dist/core/plan.js +242 -0
  29. package/dist/core/tools.js +62 -0
  30. package/dist/core/types.js +11 -0
  31. package/dist/core/updates.js +121 -0
  32. package/docs/arquitetura.md +80 -0
  33. package/docs/compatibilidade.md +50 -0
  34. package/docs/formato.md +155 -0
  35. package/docs/instalacao.md +105 -0
  36. package/docs/manutencao.md +115 -0
  37. package/package.json +43 -4
  38. package/seedgridKB/SeedGrid/Extensions/Catalogo-de-enums.md +161 -0
  39. package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -0
  40. package/seedgridKB/SeedGrid/Extensions/Indice.md +63 -0
  41. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +124 -0
  42. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Enums.md +40 -0
  43. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +97 -0
  44. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +114 -0
  45. package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao-Rust.md +69 -0
  46. package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao.md +88 -0
  47. package/seedgridKB/SeedGrid/Extensions/apikeys/Enums.md +33 -0
  48. package/seedgridKB/SeedGrid/Extensions/apikeys/Exemplos.md +121 -0
  49. package/seedgridKB/SeedGrid/Extensions/apikeys/Rust.md +126 -0
  50. package/seedgridKB/SeedGrid/Extensions/apikeys/Visao-geral.md +111 -0
  51. package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -0
  52. package/seedgridKB/SeedGrid/Extensions/audit/Configuracao.md +127 -0
  53. package/seedgridKB/SeedGrid/Extensions/audit/Enums.md +66 -0
  54. package/seedgridKB/SeedGrid/Extensions/audit/Exemplos.md +151 -0
  55. package/seedgridKB/SeedGrid/Extensions/audit/Rust.md +120 -0
  56. package/seedgridKB/SeedGrid/Extensions/audit/Visao-geral.md +114 -0
  57. package/seedgridKB/SeedGrid/Extensions/backup/Configuracao-Rust.md +136 -0
  58. package/seedgridKB/SeedGrid/Extensions/backup/Configuracao.md +319 -0
  59. package/seedgridKB/SeedGrid/Extensions/backup/Enums.md +131 -0
  60. package/seedgridKB/SeedGrid/Extensions/backup/Exemplos.md +210 -0
  61. package/seedgridKB/SeedGrid/Extensions/backup/Rust.md +135 -0
  62. package/seedgridKB/SeedGrid/Extensions/backup/Visao-geral.md +150 -0
  63. package/seedgridKB/SeedGrid/Extensions/billing/Configuracao-Rust.md +86 -0
  64. package/seedgridKB/SeedGrid/Extensions/billing/Configuracao.md +243 -0
  65. package/seedgridKB/SeedGrid/Extensions/billing/Enums.md +165 -0
  66. package/seedgridKB/SeedGrid/Extensions/billing/Exemplos.md +205 -0
  67. package/seedgridKB/SeedGrid/Extensions/billing/Rust.md +109 -0
  68. package/seedgridKB/SeedGrid/Extensions/billing/Visao-geral.md +181 -0
  69. package/seedgridKB/SeedGrid/Extensions/broker/Configuracao-Rust.md +40 -0
  70. package/seedgridKB/SeedGrid/Extensions/broker/Visao-geral.md +114 -0
  71. package/seedgridKB/SeedGrid/Extensions/cache/Configuracao-Rust.md +73 -0
  72. package/seedgridKB/SeedGrid/Extensions/cache/Configuracao.md +112 -0
  73. package/seedgridKB/SeedGrid/Extensions/cache/Enums.md +36 -0
  74. package/seedgridKB/SeedGrid/Extensions/cache/Exemplos.md +149 -0
  75. package/seedgridKB/SeedGrid/Extensions/cache/Rust.md +81 -0
  76. package/seedgridKB/SeedGrid/Extensions/cache/Visao-geral.md +90 -0
  77. package/seedgridKB/SeedGrid/Extensions/commons/Configuracao.md +107 -0
  78. package/seedgridKB/SeedGrid/Extensions/commons/Enums.md +105 -0
  79. package/seedgridKB/SeedGrid/Extensions/commons/Exemplos.md +141 -0
  80. package/seedgridKB/SeedGrid/Extensions/commons/Visao-geral.md +99 -0
  81. package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +122 -0
  82. package/seedgridKB/SeedGrid/Extensions/exchange/Enums.md +29 -0
  83. package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +82 -0
  84. package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +118 -0
  85. package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao-Rust.md +110 -0
  86. package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao.md +157 -0
  87. package/seedgridKB/SeedGrid/Extensions/filemanagement/Exemplos.md +157 -0
  88. package/seedgridKB/SeedGrid/Extensions/filemanagement/Rust.md +70 -0
  89. package/seedgridKB/SeedGrid/Extensions/filemanagement/Visao-geral.md +88 -0
  90. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao-Rust.md +124 -0
  91. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +195 -0
  92. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Enums.md +71 -0
  93. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Exemplos.md +149 -0
  94. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Rust.md +139 -0
  95. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +130 -0
  96. package/seedgridKB/SeedGrid/Extensions/notification/Configuracao.md +141 -0
  97. package/seedgridKB/SeedGrid/Extensions/notification/Enums.md +52 -0
  98. package/seedgridKB/SeedGrid/Extensions/notification/Exemplos.md +141 -0
  99. package/seedgridKB/SeedGrid/Extensions/notification/Visao-geral.md +113 -0
  100. package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao-Rust.md +67 -0
  101. package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao.md +94 -0
  102. package/seedgridKB/SeedGrid/Extensions/privacy/Enums.md +79 -0
  103. package/seedgridKB/SeedGrid/Extensions/privacy/Exemplos.md +172 -0
  104. package/seedgridKB/SeedGrid/Extensions/privacy/Rust.md +107 -0
  105. package/seedgridKB/SeedGrid/Extensions/privacy/Visao-geral.md +90 -0
  106. package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao-Rust.md +72 -0
  107. package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao.md +104 -0
  108. package/seedgridKB/SeedGrid/Extensions/realtime/Exemplos.md +104 -0
  109. package/seedgridKB/SeedGrid/Extensions/realtime/Rust.md +69 -0
  110. package/seedgridKB/SeedGrid/Extensions/realtime/Visao-geral.md +105 -0
  111. package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao-Rust.md +68 -0
  112. package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -0
  113. package/seedgridKB/SeedGrid/Extensions/resilience/Enums.md +96 -0
  114. package/seedgridKB/SeedGrid/Extensions/resilience/Exemplos.md +229 -0
  115. package/seedgridKB/SeedGrid/Extensions/resilience/Rust.md +80 -0
  116. package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -0
  117. package/seedgridKB/SeedGrid/Extensions/runtime/Configuracao-Rust.md +90 -0
  118. package/seedgridKB/SeedGrid/Extensions/runtime/Visao-geral.md +114 -0
  119. package/seedgridKB/SeedGrid/Extensions/security/Configuracao-Rust.md +327 -0
  120. package/seedgridKB/SeedGrid/Extensions/security/Configuracao.md +510 -0
  121. package/seedgridKB/SeedGrid/Extensions/security/Enums.md +95 -0
  122. package/seedgridKB/SeedGrid/Extensions/security/Exemplos.md +218 -0
  123. package/seedgridKB/SeedGrid/Extensions/security/Rust.md +203 -0
  124. package/seedgridKB/SeedGrid/Extensions/security/Visao-geral.md +131 -0
  125. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust.md +188 -0
  126. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +345 -0
  127. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Enums.md +117 -0
  128. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Exemplos.md +205 -0
  129. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Rust.md +161 -0
  130. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +142 -0
  131. package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao-Rust.md +75 -0
  132. package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao.md +152 -0
  133. package/seedgridKB/SeedGrid/Extensions/whatsapp/Enums.md +198 -0
  134. package/seedgridKB/SeedGrid/Extensions/whatsapp/Exemplos.md +173 -0
  135. package/seedgridKB/SeedGrid/Extensions/whatsapp/Rust.md +83 -0
  136. package/seedgridKB/SeedGrid/Extensions/whatsapp/Visao-geral.md +99 -0
  137. package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +112 -0
  138. package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -0
  139. package/seedgridKB/SeedGrid/SeedGrid-components/buttons/API.md +257 -0
  140. package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Exemplos.md +307 -0
  141. package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Visao-geral.md +51 -0
  142. package/seedgridKB/SeedGrid/SeedGrid-components/datatable/API.md +174 -0
  143. package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Exemplos.md +236 -0
  144. package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Visao-geral.md +76 -0
  145. package/seedgridKB/SeedGrid/SeedGrid-components/digits/API.md +339 -0
  146. package/seedgridKB/SeedGrid/SeedGrid-components/digits/Exemplos.md +311 -0
  147. package/seedgridKB/SeedGrid/SeedGrid-components/digits/Visao-geral.md +57 -0
  148. package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +125 -0
  149. package/seedgridKB/SeedGrid/SeedGrid-components/editor/Exemplos.md +116 -0
  150. package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -0
  151. package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/API.md +490 -0
  152. package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Exemplos.md +544 -0
  153. package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Visao-geral.md +69 -0
  154. package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/API.md +113 -0
  155. package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Exemplos.md +159 -0
  156. package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Visao-geral.md +92 -0
  157. package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/API.md +516 -0
  158. package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -0
  159. package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Visao-geral.md +80 -0
  160. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/API.md +615 -0
  161. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Exemplos.md +384 -0
  162. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Visao-geral.md +111 -0
  163. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/API.md +661 -0
  164. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Exemplos.md +312 -0
  165. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -0
  166. package/seedgridKB/SeedGrid/SeedGrid-components/layout/API.md +1090 -0
  167. package/seedgridKB/SeedGrid/SeedGrid-components/layout/Exemplos.md +1172 -0
  168. package/seedgridKB/SeedGrid/SeedGrid-components/layout/Visao-geral.md +98 -0
  169. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/Visao-geral.md +98 -0
  170. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-apikeys.md +118 -0
  171. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-audit.md +88 -0
  172. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-backup.md +131 -0
  173. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-platform.md +132 -0
  174. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-user.md +120 -0
  175. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-login-tenancy.md +119 -0
  176. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-security.md +427 -0
  177. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-self-enter.md +104 -0
  178. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-subdomain-tenancy.md +110 -0
  179. package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/API.md +490 -0
  180. package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +561 -0
  181. package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Visao-geral.md +82 -0
  182. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/Visao-geral.md +68 -0
  183. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-commons.md +61 -0
  184. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-core.md +140 -0
  185. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-playground.md +112 -0
  186. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-theme.md +97 -0
  187. package/seedgridKB/SeedGrid/SeedGrid-components/providers/API.md +177 -0
  188. package/seedgridKB/SeedGrid/SeedGrid-components/providers/Exemplos.md +332 -0
  189. package/seedgridKB/SeedGrid/SeedGrid-components/providers/Visao-geral.md +106 -0
  190. package/seedgridKB/SeedGrid/SeedGrid-components/showcase.md +70 -0
  191. package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +53 -0
  192. package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +45 -0
  193. package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +43 -0
  194. package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +41 -0
  195. package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +44 -0
  196. package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +40 -0
  197. package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +78 -0
  198. package/seedgridKB/SeedGrid/Toolkit/references/glossario.md +15 -0
  199. package/seedgridKB/SeedGrid/Toolkit/references/modos-shared-native.md +27 -0
  200. package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +54 -0
  201. package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +39 -0
  202. package/seedgridKB/SeedGrid/Toolkit/rules/formato-entrega.md +29 -0
  203. package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +47 -0
  204. package/seedgridKB/SeedGrid/Toolkit/rules/personalizacao-local.md +22 -0
  205. package/seedgridKB/SeedGrid/Toolkit/skills/planejar-mudanca/SKILL.md +38 -0
  206. package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/SKILL.md +33 -0
  207. package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/references/checklist.md +25 -0
  208. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-backend-crud/SKILL.md +29 -0
  209. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-bump/SKILL.md +26 -0
  210. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/SKILL.md +221 -0
  211. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/agents/openai.yaml +4 -0
  212. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/checklist.md +31 -0
  213. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/patterns.md +185 -0
  214. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/routing.md +66 -0
  215. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/SKILL.md +56 -0
  216. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +4 -0
  217. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/checklist.md +32 -0
  218. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/steps.md +114 -0
  219. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-review/SKILL.md +61 -0
  220. package/seedgridKB/SeedGrid/Toolkit/skills/validar-mudanca/SKILL.md +29 -0
  221. package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +29 -0
  222. package/seedgridKB/SeedGrid/Toolkit/templates/especificacao-tarefa.md +32 -0
  223. package/seedgridKB/SeedGrid/Toolkit/templates/relatorio-validacao.md +30 -0
  224. package/seedgridKB/SeedGrid/Versoes.md +64 -0
  225. package/seedgridKB/SeedGrid/Visao-geral.md +35 -0
  226. package/seedgridKB/manifest.json +142 -0
@@ -0,0 +1,64 @@
1
+ import { normalizeText } from './hash.js';
2
+ export const BLOCK_BEGIN = '<!-- BEGIN SEEDGRID AI TOOLKIT -->';
3
+ export const BLOCK_END = '<!-- END SEEDGRID AI TOOLKIT -->';
4
+ function count(text, needle) {
5
+ let n = 0;
6
+ for (let i = text.indexOf(needle); i >= 0; i = text.indexOf(needle, i + needle.length))
7
+ n++;
8
+ return n;
9
+ }
10
+ /** Localiza o bloco gerenciado. start/end delimitam o bloco, marcadores inclusive. */
11
+ export function scanBlock(text) {
12
+ const begins = count(text, BLOCK_BEGIN);
13
+ const ends = count(text, BLOCK_END);
14
+ if (begins === 0 && ends === 0)
15
+ return { status: 'none' };
16
+ if (begins !== 1 || ends !== 1) {
17
+ return { status: 'invalid', reason: `marcadores duplicados ou incompletos (BEGIN=${begins}, END=${ends})` };
18
+ }
19
+ const start = text.indexOf(BLOCK_BEGIN);
20
+ const endMarker = text.indexOf(BLOCK_END);
21
+ if (endMarker < start)
22
+ return { status: 'invalid', reason: 'o marcador END aparece antes de BEGIN' };
23
+ const inner = text.slice(start + BLOCK_BEGIN.length, endMarker);
24
+ return { status: 'one', start, end: endMarker + BLOCK_END.length, inner: canonicalInner(inner) };
25
+ }
26
+ /** Forma canônica do conteúdo interno, usada no hash e nas comparações. */
27
+ export function canonicalInner(inner) {
28
+ return normalizeText(inner).replace(/^\n+/, '').replace(/\n+$/, '');
29
+ }
30
+ function detectEol(text) {
31
+ return text.includes('\r\n') ? '\r\n' : '\n';
32
+ }
33
+ export function renderBlock(inner, eol = '\n') {
34
+ const body = canonicalInner(inner).split('\n').join(eol);
35
+ return `${BLOCK_BEGIN}${eol}${body}${eol}${BLOCK_END}`;
36
+ }
37
+ /** Insere ou substitui o bloco, preservando todo o texto fora dos marcadores. */
38
+ export function upsertBlock(text, inner) {
39
+ if (text === null || text.length === 0)
40
+ return `${renderBlock(inner)}\n`;
41
+ const eol = detectEol(text);
42
+ const scan = scanBlock(text);
43
+ if (scan.status === 'invalid')
44
+ throw new Error(scan.reason);
45
+ if (scan.status === 'one') {
46
+ return text.slice(0, scan.start) + renderBlock(inner, eol) + text.slice(scan.end);
47
+ }
48
+ const sep = text.endsWith(eol) ? eol : eol + eol;
49
+ return `${text}${sep}${renderBlock(inner, eol)}${eol}`;
50
+ }
51
+ /** Remove o bloco e a quebra de linha que o acompanhava, preservando o restante. */
52
+ export function removeBlock(text) {
53
+ const scan = scanBlock(text);
54
+ if (scan.status !== 'one')
55
+ return text;
56
+ const eol = detectEol(text);
57
+ let before = text.slice(0, scan.start);
58
+ let after = text.slice(scan.end);
59
+ if (after.startsWith(eol))
60
+ after = after.slice(eol.length);
61
+ if ((after.length === 0 || after.startsWith(eol)) && before.endsWith(eol + eol))
62
+ before = before.slice(0, -eol.length);
63
+ return before + after;
64
+ }
@@ -0,0 +1,528 @@
1
+ import { lstat, readdir, readFile, realpath } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parseFrontmatter } from './frontmatter.js';
4
+ import { isInside, isSafeRelPath } from './paths.js';
5
+ import { ToolkitError } from './types.js';
6
+ export const CONTENT_SCHEMA_VERSION = 2;
7
+ /** Nome no padrão Agent Skills: 1-64, [a-z0-9-], sem hífen nas pontas nem "--". */
8
+ export const NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
9
+ export const MAX_NAME_LENGTH = 64;
10
+ export const AGENT_CAPABILITIES = ['read-files', 'edit-files', 'run-checks'];
11
+ /** Campos de frontmatter de skill aceitos no conteúdo compartilhado (portáveis). */
12
+ export const PORTABLE_SKILL_KEYS = ['name', 'description', 'license', 'compatibility', 'metadata', 'allowed-tools'];
13
+ const AGENT_KEYS = ['id', 'description', 'capabilities', 'preferred_skills'];
14
+ export const AGENT_REQUIRED_SECTIONS = [
15
+ 'Objetivo',
16
+ 'Quando usar',
17
+ 'Entradas necessárias',
18
+ 'Responsabilidades',
19
+ 'Limites',
20
+ 'Formato da entrega',
21
+ ];
22
+ const MAX_AGENT_DESCRIPTION = 300;
23
+ const SHARED_TYPES = ['agents', 'rules', 'templates', 'references'];
24
+ /** Padrões que nunca devem ir para um pacote público (verificados em todo o conteúdo). */
25
+ const SECRET_PATTERNS = [
26
+ [/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{10,}/, 'chave Stripe'],
27
+ [/\bwhsec_[A-Za-z0-9]{10,}/, 'segredo de webhook Stripe'],
28
+ [/\bAKIA[0-9A-Z]{16}\b/, 'chave de acesso AWS'],
29
+ [/\bgh[pousr]_[A-Za-z0-9]{30,}/, 'token do GitHub'],
30
+ [/-----BEGIN [A-Z ]*PRIVATE KEY-----\s*[A-Za-z0-9+/=]{40,}/, 'chave privada'],
31
+ ];
32
+ const LOCAL_PATH = /[A-Za-z]:[\\/](?:Users|empreendedorismo|mcps|Projetos)\b|\/[cd]\/(?:Users|empreendedorismo|mcps)\b/;
33
+ /** Índices gerados (um por pasta); nomes reservados que não podem constar no manifesto. */
34
+ export const GENERATED_INDEX_FILES = SHARED_TYPES.map((t) => `${t}/index.md`);
35
+ const MAX_DESCRIPTION = 300;
36
+ function isRecord(v) {
37
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
38
+ }
39
+ async function readContentFile(ctx, relUnderBase, where, base = ctx.base) {
40
+ if (!isSafeRelPath(relUnderBase)) {
41
+ ctx.errors.push(`${where}: caminho inválido ${JSON.stringify(relUnderBase)}`);
42
+ return null;
43
+ }
44
+ const rel = base ? `${base}/${relUnderBase}` : relUnderBase;
45
+ const abs = path.join(ctx.root, ...rel.split('/'));
46
+ try {
47
+ const st = await lstat(abs);
48
+ if (st.isSymbolicLink()) {
49
+ ctx.errors.push(`${where}: ${rel} é link simbólico; use arquivos regulares no conteúdo`);
50
+ return null;
51
+ }
52
+ if (!st.isFile()) {
53
+ ctx.errors.push(`${where}: ${rel} não é um arquivo`);
54
+ return null;
55
+ }
56
+ const real = await realpath(abs);
57
+ if (!isInside(ctx.rootReal, real)) {
58
+ ctx.errors.push(`${where}: ${rel} fica fora da pasta de conteúdo`);
59
+ return null;
60
+ }
61
+ const buf = await readFile(abs);
62
+ const text = buf.toString('utf8');
63
+ if (Buffer.compare(Buffer.from(text, 'utf8'), buf) !== 0) {
64
+ ctx.errors.push(`${where}: ${rel} não é UTF-8 válido`);
65
+ return null;
66
+ }
67
+ ctx.sources.add(rel);
68
+ for (const [re, label] of SECRET_PATTERNS) {
69
+ if (re.test(text))
70
+ ctx.errors.push(`${where}: ${rel} parece conter ${label}; remova antes de distribuir`);
71
+ }
72
+ return text;
73
+ }
74
+ catch {
75
+ ctx.errors.push(`${where}: arquivo ausente ${rel}`);
76
+ return null;
77
+ }
78
+ }
79
+ async function listFilesRecursive(dir, prefix = '') {
80
+ let entries;
81
+ try {
82
+ entries = await readdir(dir, { withFileTypes: true });
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ const out = [];
88
+ for (const e of entries) {
89
+ const rel = prefix ? `${prefix}/${e.name}` : e.name;
90
+ if (e.isDirectory())
91
+ out.push(...(await listFilesRecursive(path.join(dir, e.name), rel)));
92
+ else
93
+ out.push(rel);
94
+ }
95
+ return out;
96
+ }
97
+ function validName(name) {
98
+ return typeof name === 'string' && name.length <= MAX_NAME_LENGTH && NAME_PATTERN.test(name);
99
+ }
100
+ function stringList(v) {
101
+ if (v === undefined)
102
+ return [];
103
+ if (!Array.isArray(v) || !v.every((x) => typeof x === 'string'))
104
+ return null;
105
+ return v;
106
+ }
107
+ /** Links Markdown relativos: [texto](destino). Ignora URLs, âncoras e e-mails. */
108
+ export function relativeLinks(markdown) {
109
+ const withoutCode = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`\n]*`/g, '');
110
+ const out = [];
111
+ for (const m of withoutCode.matchAll(/\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) {
112
+ const target = m[1];
113
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith('#'))
114
+ continue;
115
+ out.push(decodeURIComponent(target.split('#')[0]));
116
+ }
117
+ return out.filter((t) => t.length > 0);
118
+ }
119
+ function checkLinks(ctx, fromRel, text, available, scope) {
120
+ for (const link of relativeLinks(text)) {
121
+ if (link.startsWith('/')) {
122
+ ctx.errors.push(`${scope}: ${fromRel} usa link absoluto ${link}`);
123
+ continue;
124
+ }
125
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(fromRel), link));
126
+ if (resolved.startsWith('..') || !available.has(resolved.replace(/\/$/, ''))) {
127
+ ctx.errors.push(`${scope}: referência quebrada em ${fromRel} -> ${link}`);
128
+ }
129
+ }
130
+ }
131
+ /** Destinos de wikilinks do Obsidian ([[destino|rótulo]], [[destino#seção]]), ignorando trechos de código. */
132
+ export function wikilinkTargets(markdown) {
133
+ const withoutCode = markdown.replace(/```[\s\S]*?```/g, '').replace(/`[^`\n]*`/g, '');
134
+ const out = [];
135
+ for (const m of withoutCode.matchAll(/!?\[\[([^\]\n]+?)\]\]/g)) {
136
+ // Dentro de tabelas Markdown o separador é escrito como "\|".
137
+ const target = m[1].replace(/\\\|/g, '|').split('|')[0].split('#')[0].split('^')[0].trim();
138
+ if (target)
139
+ out.push(target);
140
+ }
141
+ return out;
142
+ }
143
+ function checkBrain(ctx, notes, entrypoints, brainBase) {
144
+ const known = new Set(notes.map((n) => n.src.replace(/\.md$/, '')));
145
+ for (const n of notes) {
146
+ if (LOCAL_PATH.test(n.content))
147
+ ctx.errors.push(`KB ${n.src}: contém caminho local de máquina; use caminhos relativos ao repositório`);
148
+ const missing = new Set();
149
+ for (const t of wikilinkTargets(n.content))
150
+ if (!known.has(t.replace(/\.md$/, '')))
151
+ missing.add(t);
152
+ for (const t of missing)
153
+ ctx.errors.push(`KB ${n.src}: wikilink sem destino na base instalada [[${t}]]`);
154
+ }
155
+ for (const e of entrypoints) {
156
+ if (!notes.some((n) => n.src === `${brainBase}/${e}`))
157
+ ctx.errors.push(`KB: entrypoint inexistente "${e}"`);
158
+ }
159
+ }
160
+ function parseAgent(ctx, file, raw) {
161
+ const where = `agente ${file}`;
162
+ const fm = parseFrontmatter(raw);
163
+ if ('error' in fm) {
164
+ ctx.errors.push(`${where}: ${fm.error}`);
165
+ return null;
166
+ }
167
+ const d = fm.data;
168
+ let ok = true;
169
+ for (const key of Object.keys(d)) {
170
+ if (!AGENT_KEYS.includes(key)) {
171
+ ctx.errors.push(`${where}: campo não suportado "${key}" (permitidos: ${AGENT_KEYS.join(', ')})`);
172
+ ok = false;
173
+ }
174
+ }
175
+ if (!validName(d.id)) {
176
+ ctx.errors.push(`${where}: id ausente ou inválido (use minúsculas, números e hífens)`);
177
+ ok = false;
178
+ }
179
+ else if (path.posix.basename(file) !== `${d.id}.md`) {
180
+ ctx.errors.push(`${where}: o nome do arquivo deve ser ${d.id}.md`);
181
+ ok = false;
182
+ }
183
+ if (typeof d.description !== 'string' || d.description.trim() === '') {
184
+ ctx.errors.push(`${where}: description obrigatória`);
185
+ ok = false;
186
+ }
187
+ else if (d.description.length > MAX_AGENT_DESCRIPTION) {
188
+ ctx.errors.push(`${where}: description excede ${MAX_AGENT_DESCRIPTION} caracteres`);
189
+ ok = false;
190
+ }
191
+ const caps = stringList(d.capabilities);
192
+ if (caps === null) {
193
+ ctx.errors.push(`${where}: capabilities deve ser uma lista de textos`);
194
+ ok = false;
195
+ }
196
+ else {
197
+ for (const c of caps) {
198
+ if (!AGENT_CAPABILITIES.includes(c)) {
199
+ ctx.errors.push(`${where}: capability desconhecida "${c}" (permitidas: ${AGENT_CAPABILITIES.join(', ')})`);
200
+ ok = false;
201
+ }
202
+ }
203
+ }
204
+ const skills = stringList(d.preferred_skills);
205
+ if (skills === null) {
206
+ ctx.errors.push(`${where}: preferred_skills deve ser uma lista de textos`);
207
+ ok = false;
208
+ }
209
+ for (const section of AGENT_REQUIRED_SECTIONS) {
210
+ const re = new RegExp(`^##\\s+${section}\\s*$`, 'mi');
211
+ if (!re.test(fm.body)) {
212
+ ctx.errors.push(`${where}: seção obrigatória ausente "## ${section}"`);
213
+ ok = false;
214
+ }
215
+ }
216
+ if (!ok)
217
+ return null;
218
+ return {
219
+ id: d.id,
220
+ description: d.description.trim(),
221
+ capabilities: (caps ?? []),
222
+ preferredSkills: skills ?? [],
223
+ file,
224
+ body: fm.body,
225
+ raw,
226
+ };
227
+ }
228
+ /** rules, templates e references precisam de description: é ela que aparece no index.md da pasta. */
229
+ function sharedDescription(ctx, file, raw) {
230
+ const where = `conteúdo ${file}`;
231
+ const fm = parseFrontmatter(raw);
232
+ if ('error' in fm) {
233
+ ctx.errors.push(`${where}: ${fm.error} (rules, templates e references exigem frontmatter com description)`);
234
+ return undefined;
235
+ }
236
+ const d = fm.data.description;
237
+ if (typeof d !== 'string' || d.trim() === '') {
238
+ ctx.errors.push(`${where}: description obrigatória no frontmatter`);
239
+ return undefined;
240
+ }
241
+ if (d.length > MAX_DESCRIPTION)
242
+ ctx.errors.push(`${where}: description excede ${MAX_DESCRIPTION} caracteres`);
243
+ return d.trim();
244
+ }
245
+ function checkSkillFrontmatter(ctx, name, raw) {
246
+ const where = `skill ${name}`;
247
+ const fm = parseFrontmatter(raw);
248
+ if ('error' in fm) {
249
+ ctx.errors.push(`${where}: SKILL.md ${fm.error}`);
250
+ return null;
251
+ }
252
+ const d = fm.data;
253
+ for (const key of Object.keys(d)) {
254
+ if (!PORTABLE_SKILL_KEYS.includes(key)) {
255
+ ctx.errors.push(`${where}: campo "${key}" não é portável; mantenha extensões no adaptador`);
256
+ }
257
+ }
258
+ if (d.name !== name)
259
+ ctx.errors.push(`${where}: o name do SKILL.md deve ser "${name}" (igual à pasta)`);
260
+ if (typeof d.description !== 'string' || d.description.trim() === '') {
261
+ ctx.errors.push(`${where}: description obrigatória`);
262
+ return null;
263
+ }
264
+ if (d.description.length > 1024)
265
+ ctx.errors.push(`${where}: description excede 1024 caracteres`);
266
+ if (d.compatibility !== undefined && (typeof d.compatibility !== 'string' || d.compatibility.length > 500)) {
267
+ ctx.errors.push(`${where}: compatibility deve ter até 500 caracteres`);
268
+ }
269
+ if (d.metadata !== undefined) {
270
+ if (!isRecord(d.metadata) || !Object.values(d.metadata).every((v) => typeof v === 'string')) {
271
+ ctx.errors.push(`${where}: metadata deve mapear textos para textos`);
272
+ }
273
+ }
274
+ return d.description.trim();
275
+ }
276
+ /**
277
+ * Carrega e valida a fonte de conteúdo. Todas as falhas são acumuladas e
278
+ * lançadas juntas; nada é instalado a partir de conteúdo inválido.
279
+ */
280
+ export async function loadContent(root) {
281
+ const absRoot = path.resolve(root);
282
+ let rootReal;
283
+ try {
284
+ rootReal = await realpath(absRoot);
285
+ }
286
+ catch {
287
+ throw new ToolkitError(`Pasta de conteúdo não encontrada: ${absRoot}`);
288
+ }
289
+ const ctx = { root: absRoot, rootReal, base: '', errors: [], sources: new Set() };
290
+ let manifest;
291
+ try {
292
+ manifest = JSON.parse(await readFile(path.join(absRoot, 'manifest.json'), 'utf8'));
293
+ }
294
+ catch (err) {
295
+ throw new ToolkitError(`manifest.json ausente ou inválido em ${absRoot}`, [err.message]);
296
+ }
297
+ if (!isRecord(manifest))
298
+ throw new ToolkitError('manifest.json deve ser um objeto');
299
+ if (manifest.schemaVersion !== CONTENT_SCHEMA_VERSION) {
300
+ throw new ToolkitError(`schemaVersion ${String(manifest.schemaVersion)} não suportado (esperado ${CONTENT_SCHEMA_VERSION})`);
301
+ }
302
+ const contentVersion = manifest.contentVersion;
303
+ if (typeof contentVersion !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(contentVersion)) {
304
+ ctx.errors.push('manifest: contentVersion deve seguir semver (ex.: 1.0.0)');
305
+ }
306
+ if (!Array.isArray(manifest.components))
307
+ throw new ToolkitError('manifest: components deve ser uma lista');
308
+ const toolkitBase = manifest.toolkitBase ?? '';
309
+ if (typeof toolkitBase !== 'string' || (toolkitBase !== '' && !isSafeRelPath(toolkitBase))) {
310
+ throw new ToolkitError('manifest: toolkitBase deve ser um caminho relativo (ex.: SeedGrid/Toolkit)');
311
+ }
312
+ ctx.base = toolkitBase;
313
+ let brain = null;
314
+ const shared = [];
315
+ const skills = [];
316
+ const agentFiles = [];
317
+ const componentIds = new Set();
318
+ const declaredSkills = new Set();
319
+ const listedByDir = new Map();
320
+ const markListed = (dir, rel) => {
321
+ if (!listedByDir.has(dir))
322
+ listedByDir.set(dir, new Set());
323
+ listedByDir.get(dir).add(rel);
324
+ };
325
+ for (const [i, comp] of manifest.components.entries()) {
326
+ const where = `manifest.components[${i}]`;
327
+ if (!isRecord(comp) || typeof comp.id !== 'string' || typeof comp.type !== 'string') {
328
+ ctx.errors.push(`${where}: id e type são obrigatórios`);
329
+ continue;
330
+ }
331
+ if (componentIds.has(comp.id))
332
+ ctx.errors.push(`${where}: id duplicado "${comp.id}"`);
333
+ componentIds.add(comp.id);
334
+ if (comp.type === 'brain') {
335
+ // Base de conhecimento: todas as notas .md da pasta base (menos exclusões) viram .ai/brain/<base>/...
336
+ const base = comp.base;
337
+ if (brain) {
338
+ ctx.errors.push(`${where}: só pode haver um componente brain`);
339
+ continue;
340
+ }
341
+ if (typeof base !== 'string' || !isSafeRelPath(base)) {
342
+ ctx.errors.push(`${where}: base inválida ${JSON.stringify(base)}`);
343
+ continue;
344
+ }
345
+ const strings = (v) => (Array.isArray(v) && v.every((x) => typeof x === 'string') ? v : null);
346
+ const exclude = strings(comp.exclude ?? []);
347
+ const entrypoints = strings(comp.entrypoints ?? []);
348
+ if (!exclude || !entrypoints || entrypoints.some((e) => !isSafeRelPath(e))) {
349
+ ctx.errors.push(`${where}: exclude e entrypoints devem ser listas de caminhos relativos`);
350
+ continue;
351
+ }
352
+ // O conteúdo do toolkit, se estiver dentro da base, é instalado em outros destinos.
353
+ if (toolkitBase.startsWith(`${base}/`))
354
+ exclude.push(`${toolkitBase.slice(base.length + 1)}/`);
355
+ const notes = [];
356
+ const all = (await listFilesRecursive(path.join(absRoot, ...base.split('/')))).sort();
357
+ for (const rel of all) {
358
+ if (rel.split('/').some((seg) => seg.startsWith('.')))
359
+ continue;
360
+ if (exclude.some((x) => (x.endsWith('/') ? rel.startsWith(x) : rel === x)))
361
+ continue;
362
+ if (!rel.endsWith('.md')) {
363
+ ctx.errors.push(`KB ${base}/${rel}: só arquivos .md são suportados na base de conhecimento`);
364
+ continue;
365
+ }
366
+ const text = await readContentFile(ctx, rel, 'KB', base);
367
+ if (text !== null)
368
+ notes.push({ path: `brain/${base}/${rel}`, src: `${base}/${rel}`, type: 'brain', component: comp.id, content: text });
369
+ }
370
+ if (notes.length === 0)
371
+ ctx.errors.push(`${where}: nenhuma nota encontrada em ${base}/`);
372
+ brain = { base, notes, entrypoints };
373
+ continue;
374
+ }
375
+ if (!Array.isArray(comp.files) || comp.files.length === 0) {
376
+ ctx.errors.push(`${where}: files deve ser uma lista não vazia`);
377
+ continue;
378
+ }
379
+ if (SHARED_TYPES.includes(comp.type)) {
380
+ const type = comp.type;
381
+ for (const f of comp.files) {
382
+ if (typeof f !== 'string' || !isSafeRelPath(f) || !f.startsWith(`${type}/`)) {
383
+ ctx.errors.push(`${where}: arquivo inválido ${JSON.stringify(f)} (deve ficar em ${type}/)`);
384
+ continue;
385
+ }
386
+ if (GENERATED_INDEX_FILES.includes(f)) {
387
+ ctx.errors.push(`${where}: ${f} é gerado automaticamente e não pode ser listado`);
388
+ continue;
389
+ }
390
+ markListed(type, f.slice(type.length + 1));
391
+ const text = await readContentFile(ctx, f, where);
392
+ if (text === null)
393
+ continue;
394
+ let description;
395
+ if (type === 'agents')
396
+ agentFiles.push(f);
397
+ else
398
+ description = sharedDescription(ctx, f, text);
399
+ shared.push({ path: f, src: ctx.base ? `${ctx.base}/${f}` : f, type, component: comp.id, content: text, description });
400
+ }
401
+ }
402
+ else if (comp.type === 'skill') {
403
+ const name = comp.name;
404
+ if (typeof name === 'string')
405
+ declaredSkills.add(name);
406
+ if (!validName(name)) {
407
+ ctx.errors.push(`${where}: nome de skill inválido ${JSON.stringify(name)}`);
408
+ continue;
409
+ }
410
+ if (comp.id !== `skill:${name}`)
411
+ ctx.errors.push(`${where}: o id da skill deve ser "skill:${name}"`);
412
+ if (skills.some((s) => s.name === name)) {
413
+ ctx.errors.push(`${where}: skill duplicada "${name}"`);
414
+ continue;
415
+ }
416
+ const files = [];
417
+ const seen = new Set();
418
+ for (const f of comp.files) {
419
+ const entry = typeof f === 'string' ? { path: f, from: undefined } : isRecord(f) ? f : null;
420
+ if (!entry || !isSafeRelPath(entry.path) || (entry.from !== undefined && !isSafeRelPath(entry.from))) {
421
+ ctx.errors.push(`${where}: arquivo inválido ${JSON.stringify(f)}`);
422
+ continue;
423
+ }
424
+ const relInSkill = entry.path;
425
+ if (seen.has(relInSkill)) {
426
+ ctx.errors.push(`${where}: arquivo repetido ${relInSkill}`);
427
+ continue;
428
+ }
429
+ seen.add(relInSkill);
430
+ // "from" permite reutilizar um template/referência sem duplicar a autoria.
431
+ const source = typeof entry.from === 'string' ? entry.from : `skills/${name}/${relInSkill}`;
432
+ if (typeof entry.from !== 'string')
433
+ markListed(`skills/${name}`, relInSkill);
434
+ const text = await readContentFile(ctx, source, `skill ${name}`);
435
+ if (text !== null)
436
+ files.push({ path: relInSkill, content: text });
437
+ }
438
+ const skillMd = files.find((f) => f.path === 'SKILL.md');
439
+ if (!seen.has('SKILL.md')) {
440
+ ctx.errors.push(`skill ${name}: SKILL.md deve constar em files`);
441
+ continue;
442
+ }
443
+ if (!skillMd)
444
+ continue;
445
+ const description = checkSkillFrontmatter(ctx, name, skillMd.content);
446
+ if (description === null)
447
+ continue;
448
+ const available = new Set(files.map((f) => f.path));
449
+ for (const f of files.filter((x) => x.path.endsWith('.md'))) {
450
+ checkLinks(ctx, f.path, f.content, available, `skill ${name}`);
451
+ }
452
+ skills.push({ name, description, files });
453
+ }
454
+ else {
455
+ ctx.errors.push(`${where}: type desconhecido "${comp.type}"`);
456
+ }
457
+ }
458
+ // Arquivos presentes na fonte mas fora do manifesto indicam esquecimento.
459
+ for (const dir of [...SHARED_TYPES.map(String), ...skills.map((s) => `skills/${s.name}`)]) {
460
+ const listed = listedByDir.get(dir) ?? new Set();
461
+ const dirAbs = path.join(absRoot, ...(ctx.base ? ctx.base.split('/') : []), ...dir.split('/'));
462
+ for (const f of await listFilesRecursive(dirAbs)) {
463
+ if (!listed.has(f))
464
+ ctx.errors.push(`arquivo não listado no manifesto: ${dir}/${f}`);
465
+ }
466
+ }
467
+ try {
468
+ for (const e of await readdir(path.join(absRoot, ...(ctx.base ? ctx.base.split('/') : []), 'skills'), { withFileTypes: true })) {
469
+ if (e.isDirectory() && !declaredSkills.has(e.name)) {
470
+ ctx.errors.push(`pasta de skill sem componente no manifesto: skills/${e.name}`);
471
+ }
472
+ }
473
+ }
474
+ catch {
475
+ // sem pasta skills
476
+ }
477
+ const agents = [];
478
+ for (const f of agentFiles) {
479
+ if (f.split('/').length !== 2) {
480
+ ctx.errors.push(`agente ${f}: papéis devem ficar diretamente em agents/`);
481
+ continue;
482
+ }
483
+ const sf = shared.find((s) => s.path === f);
484
+ const agent = parseAgent(ctx, f, sf.content);
485
+ if (!agent)
486
+ continue;
487
+ if (agents.some((a) => a.id === agent.id))
488
+ ctx.errors.push(`agente ${f}: id duplicado "${agent.id}"`);
489
+ else
490
+ agents.push(agent);
491
+ }
492
+ for (const a of agents) {
493
+ for (const s of a.preferredSkills) {
494
+ if (!skills.some((k) => k.name === s))
495
+ ctx.errors.push(`agente ${a.id}: preferred_skills referencia skill inexistente "${s}"`);
496
+ }
497
+ }
498
+ // Links entre arquivos compartilhados são verificados no layout instalado (.ai/).
499
+ const sharedAvailable = new Set([...shared.map((s) => s.path), ...(brain?.notes.map((n) => n.path) ?? []), ...GENERATED_INDEX_FILES]);
500
+ for (const p of [...sharedAvailable]) {
501
+ const parts = p.split('/');
502
+ for (let k = 1; k < parts.length; k++)
503
+ sharedAvailable.add(parts.slice(0, k).join('/'));
504
+ }
505
+ for (const s of shared) {
506
+ if (s.path.endsWith('.md'))
507
+ checkLinks(ctx, s.path, s.content, sharedAvailable, 'conteúdo');
508
+ }
509
+ if (brain) {
510
+ checkBrain(ctx, brain.notes, brain.entrypoints, brain.base);
511
+ shared.push(...brain.notes);
512
+ }
513
+ if (ctx.errors.length > 0) {
514
+ throw new ToolkitError(`Conteúdo inválido em ${absRoot}`, ctx.errors);
515
+ }
516
+ return {
517
+ root: absRoot,
518
+ schemaVersion: CONTENT_SCHEMA_VERSION,
519
+ contentVersion: contentVersion,
520
+ shared,
521
+ agents: agents.sort((a, b) => a.id.localeCompare(b.id)),
522
+ skills: skills.sort((a, b) => a.name.localeCompare(b.name)),
523
+ brain: brain
524
+ ? { notes: brain.notes.length, entrypoints: brain.entrypoints.map((e) => `.ai/brain/${brain.base}/${e}`) }
525
+ : null,
526
+ sourceFiles: [...ctx.sources].sort(),
527
+ };
528
+ }
@@ -0,0 +1,40 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { ToolkitError } from './types.js';
5
+ /** Raiz do pacote (pasta que contém package.json, dist/ e seedgridKB/). */
6
+ export function packageRoot() {
7
+ // dist/core/env.js ou build-test/src/core/env.js -> sobe até achar package.json
8
+ let dir = path.dirname(fileURLToPath(import.meta.url));
9
+ for (let i = 0; i < 5; i++) {
10
+ try {
11
+ statSync(path.join(dir, 'package.json'));
12
+ return dir;
13
+ }
14
+ catch {
15
+ dir = path.dirname(dir);
16
+ }
17
+ }
18
+ throw new ToolkitError('package.json do toolkit não encontrado');
19
+ }
20
+ export function cliVersion() {
21
+ const pkg = JSON.parse(readFileSync(path.join(packageRoot(), 'package.json'), 'utf8'));
22
+ return pkg.version;
23
+ }
24
+ /** Conteúdo empacotado junto com o CLI. A versão do CLI identifica este conteúdo. */
25
+ export function bundledContentDir() {
26
+ return path.join(packageRoot(), 'seedgridKB');
27
+ }
28
+ export function resolveProject(project) {
29
+ const abs = path.resolve(project ?? process.cwd());
30
+ let st;
31
+ try {
32
+ st = statSync(abs);
33
+ }
34
+ catch {
35
+ throw new ToolkitError(`Pasta do projeto não encontrada: ${abs}`);
36
+ }
37
+ if (!st.isDirectory())
38
+ throw new ToolkitError(`O caminho do projeto não é uma pasta: ${abs}`);
39
+ return abs;
40
+ }
@@ -0,0 +1,29 @@
1
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
2
+ import { normalizeText } from './hash.js';
3
+ /** Lê frontmatter YAML. O "---" de abertura precisa ser a primeira linha. */
4
+ export function parseFrontmatter(text) {
5
+ const src = normalizeText(text).replace(/^/, '');
6
+ if (!src.startsWith('---\n'))
7
+ return { error: 'o arquivo não começa com "---" na primeira linha' };
8
+ const end = src.indexOf('\n---', 3);
9
+ if (end < 0)
10
+ return { error: 'frontmatter sem "---" de fechamento' };
11
+ const after = src.slice(end + 4);
12
+ if (after.length > 0 && !after.startsWith('\n'))
13
+ return { error: 'linha de fechamento do frontmatter inválida' };
14
+ let data;
15
+ try {
16
+ data = parseYaml(src.slice(4, end + 1));
17
+ }
18
+ catch (err) {
19
+ return { error: `YAML inválido: ${err.message.split('\n')[0]}` };
20
+ }
21
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
22
+ return { error: 'o frontmatter deve ser um mapa YAML' };
23
+ }
24
+ return { data: data, body: after.replace(/^\n/, '') };
25
+ }
26
+ export function renderFrontmatter(data, body) {
27
+ const yaml = stringifyYaml(data, { lineWidth: 0 }).trimEnd();
28
+ return `---\n${yaml}\n---\n${body}`;
29
+ }
@@ -0,0 +1,14 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * Normaliza finais de linha antes do hash. Assim, checkouts com CRLF
4
+ * (core.autocrlf no Windows) não são confundidos com customizações.
5
+ */
6
+ export function normalizeText(text) {
7
+ return text.replace(/\r\n/g, '\n');
8
+ }
9
+ export function sha256(text) {
10
+ return createHash('sha256').update(normalizeText(text), 'utf8').digest('hex');
11
+ }
12
+ export function isSha256(value) {
13
+ return typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
14
+ }