jarvis-ai-framework 2.0.0 → 2.1.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 (192) hide show
  1. package/AGENTS.md +12 -9
  2. package/agents/AGENTS.md +9 -1
  3. package/agents/README.md +2 -2
  4. package/agents/engineering/data/eng.data-engineer.agent.md +2 -2
  5. package/agents/engineering/eng.agent.md +4 -6
  6. package/agents/engineering/eng.bug-hunter.md +10 -5
  7. package/agents/engineering/eng.cybersecurity.agent.md +2 -4
  8. package/agents/engineering/eng.dev-code-reviewer.md +4 -0
  9. package/agents/engineering/eng.docs-writer.md +3 -3
  10. package/agents/engineering/eng.frontend.agent.md +17 -8
  11. package/agents/engineering/eng.rpa.agent.md +3 -5
  12. package/agents/engineering/eng.ux-designer.agent.md +4 -0
  13. package/agents/engineering/qa/eng.qa.cypress-specialist.md +8 -5
  14. package/agents/engineering/qa/eng.qa.quality-champion-task-agent.md +4 -0
  15. package/agents/engineering/qa/eng.qa.quality-strategist.md +18 -8
  16. package/agents/engineering/qa/eng.qa.test-architect.md +17 -6
  17. package/agents/engineering/qa/eng.qa.test-planner.md +17 -8
  18. package/agents/engineering/qa/eng.qa.testing-engineer.md +26 -9
  19. package/agents/product/prod.discovery-interviewer.md +22 -3
  20. package/agents/product/prod.pm-checker.md +4 -0
  21. package/bin/commands/map.js +43 -0
  22. package/bin/commands/tokens.js +93 -0
  23. package/bin/jarvis.js +10 -0
  24. package/bin/lib/config/constants.js +11 -0
  25. package/bin/lib/core/profile-filter.js +113 -0
  26. package/bin/lib/core/scanner.js +2 -2
  27. package/bin/lib/core/token-report.js +90 -0
  28. package/bin/lib/flow-map/index.js +65 -0
  29. package/bin/lib/flow-map/parse.js +217 -0
  30. package/bin/lib/flow-map/render.js +86 -0
  31. package/bin/lib/flow-map/tree.js +199 -0
  32. package/members.md +1 -1
  33. package/package.json +4 -2
  34. package/rules/AGENTS.md +18 -7
  35. package/rules/engineering/eng-rules.md +2 -12
  36. package/rules/engineering/eng-security-rules.md +1 -2
  37. package/rules/engineering/eng.integrations-rules.md +0 -4
  38. package/rules/engineering/eng.skills-rules.md +0 -4
  39. package/rules/engineering/eng.specializations-rules.md +0 -4
  40. package/rules/product/README.md +1 -0
  41. package/rules/product/prod-rules.md +1 -4
  42. package/rules/product/prod.spec-rules.md +0 -4
  43. package/rules/rtk-rules.md +0 -1
  44. package/{rules → rules-on-demand}/engineering/eng.breakdown-subtasks-rules.md +0 -4
  45. package/{rules → rules-on-demand}/engineering/eng.plan-rules.md +0 -4
  46. package/{rules → rules-on-demand}/engineering/eng.pr-rules.md +0 -4
  47. package/{rules → rules-on-demand}/engineering/eng.pre-pr-rules.md +0 -4
  48. package/{rules → rules-on-demand}/engineering/eng.start-rules.md +0 -4
  49. package/{rules → rules-on-demand}/engineering/eng.tech-spec-rules.md +1 -2
  50. package/{rules → rules-on-demand}/engineering/eng.work-rules.md +0 -4
  51. package/skills/AGENTS.md +22 -21
  52. package/skills/SKILLS-ROADMAP.md +0 -4
  53. package/skills/eng-ai-engineer/SKILL.md +6 -0
  54. package/skills/eng-backend/SKILL.md +6 -0
  55. package/skills/eng-backend-arch-c4/SKILL.md +10 -4
  56. package/skills/eng-backend-microservices-trace/SKILL.md +6 -0
  57. package/skills/eng-backend-nestjs/SKILL.md +16 -461
  58. package/skills/eng-backend-nestjs/references/1-arquitetura-modulos.md +54 -0
  59. package/skills/eng-backend-nestjs/references/2-guards-autenticacao.md +118 -0
  60. package/skills/eng-backend-nestjs/references/3-interceptors-pipes-filters.md +113 -0
  61. package/skills/eng-backend-nestjs/references/4-config-logging.md +61 -0
  62. package/skills/eng-backend-nestjs/references/5-testes.md +83 -0
  63. package/skills/eng-backend-nestjs/references/6-problemas-comuns.md +53 -0
  64. package/skills/eng-backend-rabbitmq/SKILL.md +15 -298
  65. package/skills/eng-backend-rabbitmq/references/1-referencia-endpoints.md +28 -0
  66. package/skills/eng-backend-rabbitmq/references/2-codigo-consumer-producer.md +90 -0
  67. package/skills/eng-backend-rabbitmq/references/3-arquiteturas-comuns.md +101 -0
  68. package/skills/eng-backend-rabbitmq/references/4-troubleshooting.md +93 -0
  69. package/skills/eng-data-bi/SKILL.md +6 -0
  70. package/skills/eng-data-debug/SKILL.md +6 -0
  71. package/skills/eng-data-engineer/SKILL.md +16 -7
  72. package/skills/eng-data-onboard/SKILL.md +6 -0
  73. package/skills/eng-data-orchestrator/SKILL.md +6 -0
  74. package/skills/eng-devops-performance-engineer/SKILL.md +6 -0
  75. package/skills/eng-frontend/SKILL.md +6 -0
  76. package/skills/eng-frontend-design-system/SKILL.md +13 -479
  77. package/skills/eng-frontend-design-system/references/1-estrutura-pacote.md +57 -0
  78. package/skills/eng-frontend-design-system/references/2-tokens.md +116 -0
  79. package/skills/eng-frontend-design-system/references/3-componentes.md +165 -0
  80. package/skills/eng-frontend-design-system/references/4-storybook.md +85 -0
  81. package/skills/eng-frontend-design-system/references/5-auditoria-versionamento.md +76 -0
  82. package/skills/eng-frontend-microfrontend/SKILL.md +17 -457
  83. package/skills/eng-frontend-microfrontend/references/1-criando-remote.md +109 -0
  84. package/skills/eng-frontend-microfrontend/references/2-configurando-shell.md +70 -0
  85. package/skills/eng-frontend-microfrontend/references/3-contratos-interface.md +73 -0
  86. package/skills/eng-frontend-microfrontend/references/4-shared-dependencies.md +48 -0
  87. package/skills/eng-frontend-microfrontend/references/5-event-bus.md +59 -0
  88. package/skills/eng-frontend-microfrontend/references/6-monorepo-deploy.md +77 -0
  89. package/skills/eng-frontend-microfrontend/references/7-troubleshooting.md +45 -0
  90. package/skills/eng-global-browser-extension-builder/SKILL.md +6 -0
  91. package/skills/eng-global-docs-write/SKILL.md +10 -5
  92. package/skills/eng-global-jira-comment/SKILL.md +6 -0
  93. package/skills/eng-global-pr/SKILL.md +7 -1
  94. package/skills/eng-global-task-comment/SKILL.md +6 -0
  95. package/skills/eng-qa-a11y-audit/SKILL.md +10 -4
  96. package/skills/eng-qa-bug-report/SKILL.md +18 -230
  97. package/skills/eng-qa-bug-report/references/1-integracao-task-managers.md +148 -0
  98. package/skills/eng-qa-bug-report/references/2-exemplos-de-uso.md +46 -0
  99. package/skills/eng-qa-bug-report/references/3-bug-to-test.md +45 -0
  100. package/skills/eng-qa-cypress-e2e/SKILL.md +6 -0
  101. package/skills/eng-qa-dev-guide/SKILL.md +9 -1
  102. package/skills/eng-qa-e2e/SKILL.md +9 -3
  103. package/skills/eng-qa-e2e-spec-writer/SKILL.md +15 -9
  104. package/skills/eng-qa-exploratory/SKILL.md +16 -9
  105. package/skills/eng-qa-gate/SKILL.md +11 -5
  106. package/skills/eng-qa-gate/assets/checklist-validacao.md +1 -1
  107. package/skills/eng-qa-graphql-contract/SKILL.md +9 -3
  108. package/skills/eng-qa-quality-report/SKILL.md +9 -3
  109. package/skills/eng-qa-test-plan/SKILL.md +6 -0
  110. package/skills/eng-qa-testsprite/SKILL.md +10 -4
  111. package/skills/eng-qa-unit-test/SKILL.md +7 -1
  112. package/skills/eng-scraper/SKILL.md +12 -436
  113. package/skills/eng-scraper/references/1-puppeteer.md +140 -0
  114. package/skills/eng-scraper/references/2-playwright-cheerio-pdf.md +82 -0
  115. package/skills/eng-scraper/references/3-anti-bot.md +94 -0
  116. package/skills/eng-scraper/references/4-resiliencia-etl-monitoramento.md +137 -0
  117. package/skills/eng-scraper-robot-builder/SKILL.md +6 -0
  118. package/skills/eng-security-cybersecurity/SKILL.md +13 -409
  119. package/skills/eng-security-cybersecurity/references/1-owasp-top-10.md +331 -0
  120. package/skills/eng-security-cybersecurity/references/2-secrets-management.md +31 -0
  121. package/skills/eng-security-cybersecurity/references/3-input-validation.md +43 -0
  122. package/skills/eng-security-cybersecurity/references/4-compliance.md +20 -0
  123. package/skills/eng-security-patch/SKILL.md +6 -0
  124. package/skills/eng-security-threat-model/SKILL.md +6 -0
  125. package/skills/eng-security-triage/SKILL.md +6 -0
  126. package/skills/jarvis-context-detect/SKILL.md +6 -0
  127. package/skills/jarvis-create-specialization/SKILL.md +6 -0
  128. package/skills/jarvis-docs-central/SKILL.md +6 -0
  129. package/skills/jarvis-docs-index/SKILL.md +10 -2
  130. package/skills/jarvis-evolution-architect/SKILL.md +439 -0
  131. package/skills/jarvis-evolution-architect/references/exemplos.md +297 -0
  132. package/skills/jarvis-evolution-architect/references/guia-de-uso.md +355 -0
  133. package/skills/jarvis-evolution-architect/references/lentes-e-conceitos.md +292 -0
  134. package/skills/jarvis-evolution-architect/references/visao-geral.md +272 -0
  135. package/skills/jarvis-init/SKILL.md +14 -7
  136. package/skills/jarvis-list-specializations/SKILL.md +6 -0
  137. package/skills/jarvis-report-issue/SKILL.md +6 -0
  138. package/skills/product-roadmap-report/SKILL.md +8 -0
  139. package/skills/product-specs/SKILL.md +9 -0
  140. package/skills/product-specs-update/SKILL.md +6 -0
  141. package/taxonomy.md +12 -12
  142. package/templates/AGENTS.md +2 -0
  143. package/templates/engineering/tech-spec-delivery-template.md +179 -0
  144. package/workflows/AGENTS.md +12 -2
  145. package/workflows/README.md +18 -16
  146. package/workflows/all-tools.md +7 -1
  147. package/workflows/engineering/data/data.contract.md +8 -0
  148. package/workflows/engineering/data/data.new-pipeline.md +10 -0
  149. package/workflows/engineering/eng.breakdown-subtasks.md +9 -0
  150. package/workflows/engineering/eng.bug-audit.md +9 -2
  151. package/workflows/engineering/eng.build-tech-spec.md +17 -146
  152. package/workflows/engineering/eng.create-ard-from-code.md +8 -0
  153. package/workflows/engineering/eng.create-ard.md +8 -0
  154. package/workflows/engineering/eng.create-rfc.md +7 -0
  155. package/workflows/engineering/eng.debug.md +3 -3
  156. package/workflows/engineering/eng.docs.md +9 -0
  157. package/workflows/engineering/eng.light-arch.md +7 -0
  158. package/workflows/engineering/eng.plan.md +15 -2
  159. package/workflows/engineering/eng.pr.md +19 -7
  160. package/workflows/engineering/eng.pre-pr.md +27 -1
  161. package/workflows/engineering/eng.review.md +13 -0
  162. package/workflows/engineering/eng.rpa.robot.md +16 -0
  163. package/workflows/engineering/eng.security-audit.md +7 -0
  164. package/workflows/engineering/eng.security-incident.md +6 -0
  165. package/workflows/engineering/eng.security-pipeline.md +9 -0
  166. package/workflows/engineering/eng.security-review.md +6 -0
  167. package/workflows/engineering/eng.start.md +22 -6
  168. package/workflows/engineering/eng.work.md +18 -2
  169. package/workflows/engineering/frontend/eng.frontend-component.md +7 -0
  170. package/workflows/engineering/frontend/eng.frontend-perf-audit.md +8 -0
  171. package/workflows/engineering/frontend/eng.frontend-review.md +8 -0
  172. package/workflows/engineering/qa/eng.qa-dev-quality-guide.md +6 -0
  173. package/workflows/engineering/qa/eng.qa-e2e-test-generation.md +6 -0
  174. package/workflows/engineering/qa/eng.qa-exploratory-session.md +8 -0
  175. package/workflows/engineering/qa/eng.qa-quality-gate-validation.md +7 -0
  176. package/workflows/engineering/qa/eng.qa-quality-report.md +7 -0
  177. package/workflows/engineering/qa/eng.qa-refinement-entry.md +7 -0
  178. package/workflows/engineering/qa/eng.qa-release-signoff.md +6 -0
  179. package/workflows/engineering/qa/eng.qa-sprint-planning.md +6 -0
  180. package/workflows/product/prod.roadmap.preview.md +9 -0
  181. package/workflows/product/prod.spec.ard.md +97 -0
  182. package/workflows/product/prod.spec.breakdown.md +10 -0
  183. package/workflows/product/prod.spec.clarify.md +8 -2
  184. package/workflows/product/prod.spec.discovery.md +10 -0
  185. package/workflows/product/prod.spec.epic.md +8 -0
  186. package/workflows/product/prod.spec.frd.md +7 -0
  187. package/workflows/product/prod.spec.issue.md +6 -0
  188. package/workflows/product/prod.spec.md +15 -1
  189. package/workflows/product/prod.spec.prd.md +7 -0
  190. package/workflows/taxonomy.md +58 -6
  191. package/workflows/warm-up.md +24 -9
  192. package/rules/engineering/eng.downstream-flow-rules.md +0 -297
@@ -145,446 +145,22 @@ curl -s "https://exemplo.com/robots.txt"
145
145
 
146
146
  > Se houver restrições legais ou éticas significativas, comunicar ao usuário antes de prosseguir.
147
147
 
148
- ### Puppeteer — Ferramenta Principal (padrão do projeto)
149
-
150
- Puppeteer é a ferramenta padrão para scraping com browser headless. Todo código de scraping deve ser em TypeScript e, quando integrado ao sistema, implementado como `@Injectable()` NestJS.
151
-
152
- #### Setup básico
153
-
154
- ```typescript
155
- import puppeteer, { Browser, Page } from 'puppeteer'
156
-
157
- async function createBrowser(): Promise<Browser> {
158
- return puppeteer.launch({
159
- headless: true,
160
- args: [
161
- '--no-sandbox',
162
- '--disable-setuid-sandbox',
163
- '--disable-blink-features=AutomationControlled',
164
- ],
165
- })
166
- }
167
-
168
- // ✅ Sempre fechar browser após uso
169
- async function withBrowser<T>(fn: (browser: Browser) => Promise<T>): Promise<T> {
170
- const browser = await createBrowser()
171
- try {
172
- return await fn(browser)
173
- } finally {
174
- await browser.close()
175
- }
176
- }
177
- ```
178
-
179
- #### NestJS — ScraperService como @Injectable
180
-
181
- ```typescript
182
- import { Injectable, Logger } from '@nestjs/common'
183
- import puppeteer, { Browser, Page } from 'puppeteer'
184
-
185
- @Injectable()
186
- export class ScraperService {
187
- private readonly logger = new Logger(ScraperService.name)
188
-
189
- async scrapeProducts(url: string): Promise<Product[]> {
190
- const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] })
191
-
192
- try {
193
- const page = await browser.newPage()
194
- await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...')
195
-
196
- await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 })
197
- await page.waitForSelector('[data-testid="product-card"]', { timeout: 10_000 })
198
-
199
- const products = await page.evaluate(() =>
200
- Array.from(document.querySelectorAll('[data-testid="product-card"]')).map((card) => ({
201
- name: card.querySelector('h2')?.textContent?.trim() ?? null,
202
- price: card.querySelector('[data-price]')?.getAttribute('data-price') ?? null,
203
- url: (card.querySelector('a') as HTMLAnchorElement)?.href ?? null,
204
- }))
205
- )
206
-
207
- const valid = products.filter((p) => p.name && p.price)
208
- if (valid.length < products.length) {
209
- this.logger.warn(`${products.length - valid.length} produtos com dados incompletos ignorados`)
210
- }
211
-
212
- return valid as Product[]
213
- } finally {
214
- await browser.close()
215
- }
216
- }
217
- }
218
- ```
219
-
220
- #### Interceptar chamadas de API interna
221
-
222
- ```typescript
223
- // ✅ Mais estável que scraping de HTML — dados estruturados direto da API interna
224
- async function interceptApiData(url: string, apiPattern: RegExp): Promise<unknown[]> {
225
- return withBrowser(async (browser) => {
226
- const page = await browser.newPage()
227
- const captured: unknown[] = []
228
-
229
- await page.setRequestInterception(true)
230
-
231
- page.on('request', (req) => req.continue())
232
-
233
- page.on('response', async (response) => {
234
- if (apiPattern.test(response.url()) && response.headers()['content-type']?.includes('json')) {
235
- try {
236
- captured.push(await response.json())
237
- } catch {
238
- // ignorar responses não-JSON
239
- }
240
- }
241
- })
242
-
243
- await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 })
244
-
245
- return captured
246
- })
247
- }
248
-
249
- // Uso: interceptar chamadas de /api/products/*
250
- const data = await interceptApiData('https://loja.com/produtos', /\/api\/products/)
251
- ```
252
-
253
- #### Paginação automática
254
-
255
- ```typescript
256
- async function scrapeAllPages(baseUrl: string): Promise<unknown[]> {
257
- return withBrowser(async (browser) => {
258
- const page = await browser.newPage()
259
- const allItems: unknown[] = []
260
- let pageNum = 1
261
- let hasNextPage = true
262
-
263
- while (hasNextPage) {
264
- await page.goto(`${baseUrl}?page=${pageNum}`, { waitUntil: 'networkidle2' })
265
-
266
- const items = await page.evaluate(() =>
267
- Array.from(document.querySelectorAll('.item')).map((el) => ({
268
- title: el.querySelector('h3')?.textContent?.trim() ?? null,
269
- }))
270
- )
271
-
272
- allItems.push(...items)
273
-
274
- hasNextPage = (await page.$('[aria-label="Próxima página"]:not([disabled])')) !== null
275
- pageNum++
276
-
277
- await randomDelay(1000, 3000)
278
- }
279
-
280
- return allItems
281
- })
282
- }
283
- ```
284
-
285
- ### Playwright — Alternativa (quando necessário)
286
-
287
- Usar Playwright quando Puppeteer não resolver o caso de uso (ex: multi-browser testing, maior controle de contexto).
288
-
289
- ```typescript
290
- import { chromium } from 'playwright'
291
-
292
- // Mesma lógica do Puppeteer — API similar com pequenas diferenças:
293
- // page.goto(url, { waitUntil: 'networkidle' }) → sem o "2" no Playwright
294
- // page.$() → page.locator() é o padrão moderno no Playwright
295
- // page.evaluate() → idêntico
296
- ```
297
-
298
- ### Cheerio — Scraping de HTML Estático
299
-
300
- ```typescript
301
- import * as cheerio from 'cheerio'
302
-
303
- async function scrapeWithCheerio(url: string) {
304
- const response = await fetch(url, {
305
- headers: {
306
- 'User-Agent': 'Mozilla/5.0 (compatible; MyBot/1.0; +https://meusite.com/bot)',
307
- 'Accept-Language': 'pt-BR,pt;q=0.9',
308
- },
309
- })
310
-
311
- if (!response.ok) {
312
- throw new Error(`HTTP ${response.status}: ${url}`)
313
- }
314
-
315
- const html = await response.text()
316
- const $ = cheerio.load(html)
317
-
318
- // ✅ Extração com Cheerio
319
- const articles = $('article.post').map((_, el) => ({
320
- title: $(el).find('h2').text().trim() || null,
321
- date: $(el).find('time').attr('datetime') || null,
322
- url: $(el).find('a.read-more').attr('href') || null,
323
- summary: $(el).find('.excerpt').text().trim() || null,
324
- })).get()
325
-
326
- return articles.filter((a) => a.title && a.url)
327
- }
328
- ```
329
-
330
- ### Parsing de PDFs
331
-
332
- ```typescript
333
- import pdfParse from 'pdf-parse'
334
- import { readFileSync } from 'fs'
335
-
336
- async function extractTextFromPdf(filePath: string): Promise<string> {
337
- const buffer = readFileSync(filePath)
338
- const { text, numpages } = await pdfParse(buffer)
339
- console.log(`PDF processado: ${numpages} páginas`)
340
- return text
341
- }
342
-
343
- // ✅ Extrair tabela de texto de PDF (regex + heurística)
344
- function extractTableFromText(text: string, headers: string[]): Record<string, string>[] {
345
- const lines = text.split('\n').map((l) => l.trim()).filter(Boolean)
346
- const results: Record<string, string>[] = []
347
-
348
- for (const line of lines) {
349
- // lógica específica para o formato do PDF alvo
350
- const match = line.match(/(\w+)\s+([\d,.]+)\s+([\d,.]+)/)
351
- if (match) {
352
- results.push({
353
- [headers[0]]: match[1],
354
- [headers[1]]: match[2],
355
- [headers[2]]: match[3],
356
- })
357
- }
358
- }
359
-
360
- return results
361
- }
362
- ```
363
-
364
- ### Técnicas Anti-Bot
365
-
366
- #### Headers realistas
367
-
368
- ```typescript
369
- // ✅ Headers que imitam browser real
370
- const realisticHeaders = {
371
- 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
372
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
373
- 'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
374
- 'Accept-Encoding': 'gzip, deflate, br',
375
- 'Connection': 'keep-alive',
376
- 'Upgrade-Insecure-Requests': '1',
377
- }
378
- ```
379
-
380
- #### Rate limiting e delays aleatórios
381
-
382
- ```typescript
383
- // ✅ Delay com jitter para comportamento orgânico
384
- function randomDelay(minMs: number, maxMs: number): Promise<void> {
385
- const ms = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs
386
- return new Promise((resolve) => setTimeout(resolve, ms))
387
- }
388
-
389
- // ✅ Controle de concorrência (evitar sobrecarga no alvo)
390
- import PQueue from 'p-queue'
391
-
392
- const queue = new PQueue({
393
- concurrency: 2, // máximo 2 requests simultâneos
394
- intervalCap: 5, // máximo 5 requests
395
- interval: 10_000, // por 10 segundos
396
- })
397
-
398
- async function scrapeUrls(urls: string[]) {
399
- return Promise.all(
400
- urls.map((url) => queue.add(async () => {
401
- await randomDelay(500, 1500)
402
- return scrapeUrl(url)
403
- }))
404
- )
405
- }
406
- ```
407
-
408
- #### Puppeteer — reduzir fingerprint de automação
409
-
410
- ```typescript
411
- import puppeteer from 'puppeteer'
412
- import { executablePath } from 'puppeteer'
413
-
414
- // ✅ Ocultar sinais de automação via page.evaluateOnNewDocument
415
- async function createStealthPage(browser: Browser): Promise<Page> {
416
- const page = await browser.newPage()
417
-
418
- await page.setUserAgent(
419
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
420
- )
421
-
422
- await page.setViewport({ width: 1366, height: 768 })
423
-
424
- // Remover propriedades que identificam automação
425
- await page.evaluateOnNewDocument(() => {
426
- Object.defineProperty(navigator, 'webdriver', { get: () => undefined })
427
- Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] })
428
- Object.defineProperty(navigator, 'languages', { get: () => ['pt-BR', 'pt', 'en'] })
429
- })
430
-
431
- return page
432
- }
433
- ```
434
-
435
- #### Rotação de proxies
436
-
437
- ```typescript
438
- // ✅ Pool de proxies com rotação
439
- const proxies = [
440
- 'http://user:pass@proxy1:8080',
441
- 'http://user:pass@proxy2:8080',
442
- 'http://user:pass@proxy3:8080',
443
- ]
444
-
445
- function getRandomProxy(): string {
446
- return proxies[Math.floor(Math.random() * proxies.length)]
447
- }
448
-
449
- const browser = await puppeteer.launch({
450
- headless: true,
451
- args: [`--proxy-server=${getRandomProxy()}`, '--no-sandbox'],
452
- })
453
- ```
454
-
455
- ### Resiliência e Retry
456
-
457
- ```typescript
458
- // ✅ Retry com backoff exponencial
459
- async function withRetry<T>(
460
- fn: () => Promise<T>,
461
- options: { attempts?: number; baseDelay?: number; label?: string } = {}
462
- ): Promise<T> {
463
- const { attempts = 3, baseDelay = 1000, label = 'operação' } = options
464
-
465
- for (let attempt = 1; attempt <= attempts; attempt++) {
466
- try {
467
- return await fn()
468
- } catch (error) {
469
- if (attempt === attempts) throw error
470
-
471
- const delay = baseDelay * Math.pow(2, attempt - 1)
472
- console.warn(`${label} falhou (tentativa ${attempt}/${attempts}). Retry em ${delay}ms...`)
473
- await new Promise((resolve) => setTimeout(resolve, delay))
474
- }
475
- }
476
-
477
- throw new Error('Unreachable')
478
- }
479
-
480
- // Uso
481
- const data = await withRetry(
482
- () => scrapePage(url),
483
- { attempts: 3, baseDelay: 2000, label: `scrape ${url}` }
484
- )
485
- ```
148
+ ### Referências (leia só o tema que a tarefa pedir)
486
149
 
487
- #### Detectar mudanças de estrutura
150
+ Leia **só** o arquivo do tema que a tarefa pedir; o restante fica fora do contexto.
488
151
 
489
- ```typescript
490
- // ✅ Validar que os dados extraídos fazem sentido
491
- function validateExtractedData(data: unknown[], schema: { field: string; required: boolean }[]) {
492
- const requiredFields = schema.filter((s) => s.required).map((s) => s.field)
152
+ | Tema | Quando ler | Arquivo |
153
+ |------|-----------|---------|
154
+ | Puppeteer | A tarefa usar Puppeteer (ferramenta principal do projeto) para navegar e extrair dados | `references/1-puppeteer.md` |
155
+ | Playwright, Cheerio e parsing de PDFs | A tarefa exigir Playwright, scraping de HTML estático com Cheerio ou extração de PDFs | `references/2-playwright-cheerio-pdf.md` |
156
+ | Técnicas Anti-Bot | O site bloquear requests (403/429, captcha, fingerprint) | `references/3-anti-bot.md` |
157
+ | Resiliência, ETL leve e monitoramento de mudanças | A tarefa precisar de retry, pipeline ETL leve ou monitoramento de mudanças no site | `references/4-resiliencia-etl-monitoramento.md` |
493
158
 
494
- const issues = data.filter((item) =>
495
- requiredFields.some((field) => !item[field as keyof typeof item])
496
- )
159
+ ### Skills invocados durante a execução do skill
497
160
 
498
- if (issues.length > 0) {
499
- const pct = Math.round((issues.length / data.length) * 100)
500
- console.warn(`ALERTA: ${issues.length}/${data.length} (${pct}%) itens com campos obrigatórios ausentes`)
501
- console.warn('Possível mudança de estrutura no site alvo. Verificar seletores.')
502
- }
503
-
504
- return data.filter((item) => requiredFields.every((f) => item[f as keyof typeof item]))
505
- }
506
- ```
507
-
508
- ### Pipelines ETL Leve
509
-
510
- #### Estrutura básica de pipeline
511
-
512
- ```typescript
513
- interface PipelineConfig {
514
- sources: string[] // URLs a processar
515
- transform: (raw: unknown) => unknown // transformação de dados
516
- output: string // caminho do arquivo de saída
517
- concurrency?: number // requests simultâneos
518
- }
519
-
520
- async function runPipeline(config: PipelineConfig) {
521
- console.log(`Iniciando pipeline: ${config.sources.length} fontes`)
522
-
523
- // Extract
524
- const rawData = await scrapeUrls(config.sources)
525
-
526
- // Transform
527
- const transformed = rawData
528
- .flat()
529
- .map(config.transform)
530
- .filter(Boolean)
531
-
532
- // Deduplicação por URL ou ID
533
- const deduped = [...new Map(transformed.map((item) => [item.id ?? item.url, item])).values()]
534
-
535
- // Load
536
- await saveToFile(config.output, deduped)
537
- console.log(`Pipeline concluído: ${deduped.length} registros salvos em ${config.output}`)
538
-
539
- return deduped
540
- }
541
- ```
542
-
543
- #### Formatos de saída
544
-
545
- ```typescript
546
- import { writeFileSync } from 'fs'
547
- import { stringify } from 'csv-stringify/sync'
548
-
549
- function saveToFile(path: string, data: unknown[]) {
550
- if (path.endsWith('.json')) {
551
- writeFileSync(path, JSON.stringify(data, null, 2), 'utf-8')
552
- } else if (path.endsWith('.csv')) {
553
- const csv = stringify(data as object[], { header: true })
554
- writeFileSync(path, csv, 'utf-8')
555
- } else if (path.endsWith('.ndjson')) {
556
- const ndjson = data.map((d) => JSON.stringify(d)).join('\n')
557
- writeFileSync(path, ndjson, 'utf-8')
558
- }
559
- }
560
- ```
561
-
562
- ### Monitoramento de Mudanças
563
-
564
- ```typescript
565
- // ✅ Detectar mudanças em páginas monitoradas
566
- async function checkForChanges(url: string, storePath: string) {
567
- const current = await scrapeWithCheerio(url)
568
- const currentHash = crypto.createHash('md5').update(JSON.stringify(current)).digest('hex')
569
-
570
- let previous: { hash: string; data: unknown; timestamp: string } | null = null
571
- try {
572
- previous = JSON.parse(readFileSync(storePath, 'utf-8'))
573
- } catch {
574
- // primeira execução
575
- }
576
-
577
- if (previous?.hash === currentHash) {
578
- console.log('Sem mudanças detectadas.')
579
- return { changed: false }
580
- }
581
-
582
- // Persistir estado atual
583
- writeFileSync(storePath, JSON.stringify({ hash: currentHash, data: current, timestamp: new Date().toISOString() }))
584
-
585
- return { changed: true, current, previous: previous?.data }
586
- }
587
- ```
161
+ | Passo | Skill | Condição |
162
+ |-------|-------|----------|
163
+ | — | Nenhuma — skill autocontida | — |
588
164
 
589
165
  ---
590
166
 
@@ -0,0 +1,140 @@
1
+ # Puppeteer
2
+
3
+ > Parte do skill `eng-scraper`. Leia este arquivo quando a tarefa usar Puppeteer (ferramenta principal do projeto) para navegar e extrair dados.
4
+
5
+ ### Puppeteer — Ferramenta Principal (padrão do projeto)
6
+
7
+ Puppeteer é a ferramenta padrão para scraping com browser headless. Todo código de scraping deve ser em TypeScript e, quando integrado ao sistema, implementado como `@Injectable()` NestJS.
8
+
9
+ #### Setup básico
10
+
11
+ ```typescript
12
+ import puppeteer, { Browser, Page } from 'puppeteer'
13
+
14
+ async function createBrowser(): Promise<Browser> {
15
+ return puppeteer.launch({
16
+ headless: true,
17
+ args: [
18
+ '--no-sandbox',
19
+ '--disable-setuid-sandbox',
20
+ '--disable-blink-features=AutomationControlled',
21
+ ],
22
+ })
23
+ }
24
+
25
+ // ✅ Sempre fechar browser após uso
26
+ async function withBrowser<T>(fn: (browser: Browser) => Promise<T>): Promise<T> {
27
+ const browser = await createBrowser()
28
+ try {
29
+ return await fn(browser)
30
+ } finally {
31
+ await browser.close()
32
+ }
33
+ }
34
+ ```
35
+
36
+ #### NestJS — ScraperService como @Injectable
37
+
38
+ ```typescript
39
+ import { Injectable, Logger } from '@nestjs/common'
40
+ import puppeteer, { Browser, Page } from 'puppeteer'
41
+
42
+ @Injectable()
43
+ export class ScraperService {
44
+ private readonly logger = new Logger(ScraperService.name)
45
+
46
+ async scrapeProducts(url: string): Promise<Product[]> {
47
+ const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] })
48
+
49
+ try {
50
+ const page = await browser.newPage()
51
+ await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...')
52
+
53
+ await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 })
54
+ await page.waitForSelector('[data-testid="product-card"]', { timeout: 10_000 })
55
+
56
+ const products = await page.evaluate(() =>
57
+ Array.from(document.querySelectorAll('[data-testid="product-card"]')).map((card) => ({
58
+ name: card.querySelector('h2')?.textContent?.trim() ?? null,
59
+ price: card.querySelector('[data-price]')?.getAttribute('data-price') ?? null,
60
+ url: (card.querySelector('a') as HTMLAnchorElement)?.href ?? null,
61
+ }))
62
+ )
63
+
64
+ const valid = products.filter((p) => p.name && p.price)
65
+ if (valid.length < products.length) {
66
+ this.logger.warn(`${products.length - valid.length} produtos com dados incompletos ignorados`)
67
+ }
68
+
69
+ return valid as Product[]
70
+ } finally {
71
+ await browser.close()
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ #### Interceptar chamadas de API interna
78
+
79
+ ```typescript
80
+ // ✅ Mais estável que scraping de HTML — dados estruturados direto da API interna
81
+ async function interceptApiData(url: string, apiPattern: RegExp): Promise<unknown[]> {
82
+ return withBrowser(async (browser) => {
83
+ const page = await browser.newPage()
84
+ const captured: unknown[] = []
85
+
86
+ await page.setRequestInterception(true)
87
+
88
+ page.on('request', (req) => req.continue())
89
+
90
+ page.on('response', async (response) => {
91
+ if (apiPattern.test(response.url()) && response.headers()['content-type']?.includes('json')) {
92
+ try {
93
+ captured.push(await response.json())
94
+ } catch {
95
+ // ignorar responses não-JSON
96
+ }
97
+ }
98
+ })
99
+
100
+ await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 })
101
+
102
+ return captured
103
+ })
104
+ }
105
+
106
+ // Uso: interceptar chamadas de /api/products/*
107
+ const data = await interceptApiData('https://loja.com/produtos', /\/api\/products/)
108
+ ```
109
+
110
+ #### Paginação automática
111
+
112
+ ```typescript
113
+ async function scrapeAllPages(baseUrl: string): Promise<unknown[]> {
114
+ return withBrowser(async (browser) => {
115
+ const page = await browser.newPage()
116
+ const allItems: unknown[] = []
117
+ let pageNum = 1
118
+ let hasNextPage = true
119
+
120
+ while (hasNextPage) {
121
+ await page.goto(`${baseUrl}?page=${pageNum}`, { waitUntil: 'networkidle2' })
122
+
123
+ const items = await page.evaluate(() =>
124
+ Array.from(document.querySelectorAll('.item')).map((el) => ({
125
+ title: el.querySelector('h3')?.textContent?.trim() ?? null,
126
+ }))
127
+ )
128
+
129
+ allItems.push(...items)
130
+
131
+ hasNextPage = (await page.$('[aria-label="Próxima página"]:not([disabled])')) !== null
132
+ pageNum++
133
+
134
+ await randomDelay(1000, 3000)
135
+ }
136
+
137
+ return allItems
138
+ })
139
+ }
140
+ ```
@@ -0,0 +1,82 @@
1
+ # Playwright, Cheerio e parsing de PDFs
2
+
3
+ > Parte do skill `eng-scraper`. Leia este arquivo quando a tarefa exigir Playwright, scraping de HTML estático com Cheerio ou extração de PDFs.
4
+
5
+ ### Playwright — Alternativa (quando necessário)
6
+
7
+ Usar Playwright quando Puppeteer não resolver o caso de uso (ex: multi-browser testing, maior controle de contexto).
8
+
9
+ ```typescript
10
+ import { chromium } from 'playwright'
11
+
12
+ // Mesma lógica do Puppeteer — API similar com pequenas diferenças:
13
+ // page.goto(url, { waitUntil: 'networkidle' }) → sem o "2" no Playwright
14
+ // page.$() → page.locator() é o padrão moderno no Playwright
15
+ // page.evaluate() → idêntico
16
+ ```
17
+
18
+ ### Cheerio — Scraping de HTML Estático
19
+
20
+ ```typescript
21
+ import * as cheerio from 'cheerio'
22
+
23
+ async function scrapeWithCheerio(url: string) {
24
+ const response = await fetch(url, {
25
+ headers: {
26
+ 'User-Agent': 'Mozilla/5.0 (compatible; MyBot/1.0; +https://meusite.com/bot)',
27
+ 'Accept-Language': 'pt-BR,pt;q=0.9',
28
+ },
29
+ })
30
+
31
+ if (!response.ok) {
32
+ throw new Error(`HTTP ${response.status}: ${url}`)
33
+ }
34
+
35
+ const html = await response.text()
36
+ const $ = cheerio.load(html)
37
+
38
+ // ✅ Extração com Cheerio
39
+ const articles = $('article.post').map((_, el) => ({
40
+ title: $(el).find('h2').text().trim() || null,
41
+ date: $(el).find('time').attr('datetime') || null,
42
+ url: $(el).find('a.read-more').attr('href') || null,
43
+ summary: $(el).find('.excerpt').text().trim() || null,
44
+ })).get()
45
+
46
+ return articles.filter((a) => a.title && a.url)
47
+ }
48
+ ```
49
+
50
+ ### Parsing de PDFs
51
+
52
+ ```typescript
53
+ import pdfParse from 'pdf-parse'
54
+ import { readFileSync } from 'fs'
55
+
56
+ async function extractTextFromPdf(filePath: string): Promise<string> {
57
+ const buffer = readFileSync(filePath)
58
+ const { text, numpages } = await pdfParse(buffer)
59
+ console.log(`PDF processado: ${numpages} páginas`)
60
+ return text
61
+ }
62
+
63
+ // ✅ Extrair tabela de texto de PDF (regex + heurística)
64
+ function extractTableFromText(text: string, headers: string[]): Record<string, string>[] {
65
+ const lines = text.split('\n').map((l) => l.trim()).filter(Boolean)
66
+ const results: Record<string, string>[] = []
67
+
68
+ for (const line of lines) {
69
+ // lógica específica para o formato do PDF alvo
70
+ const match = line.match(/(\w+)\s+([\d,.]+)\s+([\d,.]+)/)
71
+ if (match) {
72
+ results.push({
73
+ [headers[0]]: match[1],
74
+ [headers[1]]: match[2],
75
+ [headers[2]]: match[3],
76
+ })
77
+ }
78
+ }
79
+
80
+ return results
81
+ }
82
+ ```