mcp-lab-agent 2.1.3 → 2.1.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.
@@ -0,0 +1,66 @@
1
+ # QA Lab Learning Hub
2
+
3
+ API centralizada para aprendizados do **mcp-lab-agent**. Permite acumular memória entre projetos e ver métricas em um dashboard.
4
+
5
+ ## Rodar
6
+
7
+ ```bash
8
+ # Via npx (sem clonar)
9
+ npx mcp-lab-agent learning-hub
10
+
11
+ # Ou local
12
+ npm run learning-hub
13
+ # ou: node learning-hub/src/server.js
14
+ ```
15
+
16
+ Porta padrão: **3847**. Acesse http://localhost:3847 para o dashboard.
17
+
18
+ ## Endpoints
19
+
20
+ | Método | Rota | Descrição |
21
+ |--------|------|-----------|
22
+ | GET | `/` | Dashboard web (taxa de sucesso, padrões, recomendações) |
23
+ | POST | `/learning` | Recebe learnings do agente |
24
+ | GET | `/patterns` | Padrões agregados (`?framework=&projectId=&limit=`) |
25
+ | GET | `/health` | Health check |
26
+
27
+ ## Configurar o agente para enviar ao Hub
28
+
29
+ No `.env` do seu projeto:
30
+
31
+ ```env
32
+ LEARNING_HUB_URL=http://localhost:3847
33
+ LEARNING_HUB_PROJECT_ID=meu-projeto
34
+ ```
35
+
36
+ O agente enviará automaticamente cada aprendizado para o Hub (assíncrono, não bloqueia).
37
+
38
+ ## Formato POST /learning
39
+
40
+ ```json
41
+ {
42
+ "learnings": [
43
+ {
44
+ "type": "element_not_rendered",
45
+ "framework": "playwright",
46
+ "request": "login flow",
47
+ "fix": "await element.waitFor({ state: 'attached' })",
48
+ "success": false,
49
+ "timestamp": "2025-03-18T..."
50
+ }
51
+ ]
52
+ }
53
+ ```
54
+
55
+ ## Armazenamento
56
+
57
+ Por padrão, dados em `./data/learnings.json`. Configure:
58
+
59
+ ```env
60
+ LEARNING_HUB_DATA=/caminho/para/data
61
+ LEARNING_HUB_PORT=3847
62
+ ```
63
+
64
+ ## Escalar
65
+
66
+ Para produção: troque `store.js` por SQLite ou Postgres. A API permanece a mesma.
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "qa-lab-learning-hub",
3
+ "version": "1.0.0",
4
+ "description": "Learning Hub - API centralizada para aprendizados do mcp-lab-agent",
5
+ "type": "module",
6
+ "main": "src/server.js",
7
+ "scripts": {
8
+ "start": "node src/server.js",
9
+ "dev": "node --watch src/server.js"
10
+ },
11
+ "keywords": ["qa", "mcp-lab-agent", "learning", "dashboard"],
12
+ "author": "",
13
+ "license": "MIT",
14
+ "engines": {
15
+ "node": ">=18"
16
+ }
17
+ }
@@ -0,0 +1,73 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>QA Lab - Learning Hub Dashboard</title>
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+ body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 1.5rem; background: #0f172a; color: #e2e8f0; }
10
+ h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
11
+ .sub { color: #94a3b8; font-size: 0.875rem; margin-bottom: 1.5rem; }
12
+ .grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
13
+ .card { background: #1e293b; border-radius: 8px; padding: 1rem; border: 1px solid #334155; }
14
+ .card h3 { margin: 0 0 0.5rem; font-size: 0.75rem; text-transform: uppercase; color: #94a3b8; }
15
+ .card .val { font-size: 1.75rem; font-weight: 700; }
16
+ .card.green .val { color: #22c55e; }
17
+ .card.yellow .val { color: #eab308; }
18
+ .card.red .val { color: #ef4444; }
19
+ .section { margin-top: 1.5rem; }
20
+ .section h2 { font-size: 1rem; margin: 0 0 0.75rem; }
21
+ ul { list-style: none; padding: 0; margin: 0; }
22
+ li { padding: 0.35rem 0; border-bottom: 1px solid #334155; display: flex; justify-content: space-between; }
23
+ li:last-child { border-bottom: none; }
24
+ .rec { background: #1e293b; border-radius: 8px; padding: 1rem; margin-top: 0.5rem; border-left: 4px solid #3b82f6; }
25
+ .error { color: #ef4444; }
26
+ .refresh { background: #3b82f6; color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.875rem; }
27
+ .refresh:hover { background: #2563eb; }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <h1>QA Lab Learning Hub</h1>
32
+ <p class="sub">Dashboard centralizado de aprendizados</p>
33
+ <button class="refresh" onclick="load()">Atualizar</button>
34
+
35
+ <div class="grid" id="metrics"></div>
36
+ <div class="section">
37
+ <h2>Padrões por tipo</h2>
38
+ <ul id="byType"></ul>
39
+ </div>
40
+ <div class="section">
41
+ <h2>Recomendações</h2>
42
+ <div id="recs"></div>
43
+ </div>
44
+
45
+ <script>
46
+ const base = window.location.origin;
47
+ async function load() {
48
+ try {
49
+ const r = await fetch(base + '/patterns?limit=500');
50
+ const d = await r.json();
51
+
52
+ document.getElementById('metrics').innerHTML = [
53
+ { k: 'Taxa de sucesso (1ª tent.)', v: d.firstAttemptSuccessRate + '%', c: d.firstAttemptSuccessRate >= 70 ? 'green' : d.firstAttemptSuccessRate >= 50 ? 'yellow' : 'red' },
54
+ { k: 'Total de aprendizados', v: d.total, c: '' },
55
+ { k: 'Testes gerados', v: d.testGenerated, c: '' },
56
+ ].map(x => `<div class="card ${x.c}"><h3>${x.k}</h3><div class="val">${x.v}</div></div>`).join('');
57
+
58
+ const byType = Object.entries(d.byType || {}).filter(([,n]) => n > 0).sort((a,b) => b[1]-a[1]);
59
+ document.getElementById('byType').innerHTML = byType.length
60
+ ? byType.map(([t,n]) => `<li><span>${t}</span><strong>${n}</strong></li>`).join('')
61
+ : '<li class="error">Nenhum aprendizado ainda</li>';
62
+
63
+ document.getElementById('recs').innerHTML = (d.recommendations || [])
64
+ .map(r => `<div class="rec">${r}</div>`).join('') || '<div class="rec">Continue gerando testes.</div>';
65
+ } catch (e) {
66
+ document.getElementById('metrics').innerHTML = `<div class="card error"><h3>Erro</h3><div class="val">${e.message}</div></div>`;
67
+ }
68
+ }
69
+ load();
70
+ setInterval(load, 30000);
71
+ </script>
72
+ </body>
73
+ </html>
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Learning Hub - API centralizada para aprendizados do mcp-lab-agent
4
+ *
5
+ * Endpoints:
6
+ * POST /learning — recebe learnings (body: { learnings: [...] } ou learnings: {...})
7
+ * GET /patterns — retorna padrões agregados (?framework=playwright&projectId=xyz&limit=100)
8
+ * GET /health — health check
9
+ *
10
+ * Uso:
11
+ * LEARNING_HUB_DATA=./data node src/server.js
12
+ * Porta padrão: 3847 (QA)
13
+ */
14
+ import http from "node:http";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { addLearnings, getAggregatedPatterns } from "./store.js";
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+ const PORT = parseInt(process.env.LEARNING_HUB_PORT || "3847", 10);
22
+
23
+ function parseBody(req) {
24
+ return new Promise((resolve, reject) => {
25
+ let body = "";
26
+ req.on("data", (c) => (body += c));
27
+ req.on("end", () => {
28
+ try {
29
+ resolve(body ? JSON.parse(body) : {});
30
+ } catch {
31
+ reject(new Error("Invalid JSON"));
32
+ }
33
+ });
34
+ req.on("error", reject);
35
+ });
36
+ }
37
+
38
+ function send(res, status, data) {
39
+ res.writeHead(status, { "Content-Type": "application/json" });
40
+ res.end(JSON.stringify(data));
41
+ }
42
+
43
+ async function handler(req, res) {
44
+ res.setHeader("Access-Control-Allow-Origin", "*");
45
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
46
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
47
+
48
+ if (req.method === "OPTIONS") {
49
+ res.writeHead(204);
50
+ res.end();
51
+ return;
52
+ }
53
+
54
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
55
+ const path = url.pathname;
56
+
57
+ try {
58
+ if (path === "/health") {
59
+ send(res, 200, { ok: true, service: "learning-hub", version: "1.0.0" });
60
+ return;
61
+ }
62
+
63
+ if (path === "/learning" && req.method === "POST") {
64
+ const body = await parseBody(req);
65
+ const learnings = body.learnings ?? (body.type ? body : null);
66
+ if (!learnings) {
67
+ send(res, 400, { error: "Missing learnings. Send { learnings: [...] } or a single learning object." });
68
+ return;
69
+ }
70
+ const entries = Array.isArray(learnings) ? learnings : [learnings];
71
+ const added = addLearnings(entries);
72
+ send(res, 201, { ok: true, added });
73
+ return;
74
+ }
75
+
76
+ if (path === "/patterns" && req.method === "GET") {
77
+ const framework = url.searchParams.get("framework");
78
+ const projectId = url.searchParams.get("projectId");
79
+ const limit = parseInt(url.searchParams.get("limit") || "500", 10);
80
+ const data = getAggregatedPatterns({ framework, projectId, limit });
81
+ send(res, 200, data);
82
+ return;
83
+ }
84
+
85
+ if (path === "/" || path === "") {
86
+ const dashboardPath = path.join(__dirname, "dashboard.html");
87
+ if (fs.existsSync(dashboardPath)) {
88
+ res.writeHead(200, { "Content-Type": "text/html" });
89
+ res.end(fs.readFileSync(dashboardPath, "utf8"));
90
+ } else {
91
+ send(res, 200, {
92
+ name: "qa-lab-learning-hub",
93
+ endpoints: {
94
+ "GET /": "Dashboard",
95
+ "POST /learning": "Recebe learnings do agente",
96
+ "GET /patterns": "Padrões agregados (?framework=&projectId=&limit=)",
97
+ "GET /health": "Health check",
98
+ },
99
+ });
100
+ }
101
+ return;
102
+ }
103
+
104
+ if (path === "/api") {
105
+ send(res, 200, {
106
+ name: "qa-lab-learning-hub",
107
+ endpoints: {
108
+ "POST /learning": "Recebe learnings do agente",
109
+ "GET /patterns": "Padrões agregados (?framework=&projectId=&limit=)",
110
+ "GET /health": "Health check",
111
+ },
112
+ });
113
+ return;
114
+ }
115
+
116
+ send(res, 404, { error: "Not found" });
117
+ } catch (err) {
118
+ send(res, 500, { error: err.message || "Internal error" });
119
+ }
120
+ }
121
+
122
+ const server = http.createServer(handler);
123
+ server.listen(PORT, () => {
124
+ console.log(`Learning Hub rodando em http://localhost:${PORT}`);
125
+ console.log(" GET / — Dashboard");
126
+ console.log(" POST /learning — envia learnings");
127
+ console.log(" GET /patterns — padrões agregados");
128
+ console.log(" GET /health — health check");
129
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Store para Learning Hub - persistência em JSON (como memory.js do agente).
3
+ * Escalável: troque por SQLite/Postgres se precisar.
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ const DATA_DIR = process.env.LEARNING_HUB_DATA || path.join(process.cwd(), "data");
9
+ const LEARNINGS_FILE = path.join(DATA_DIR, "learnings.json");
10
+ const MAX_LEARNINGS = 5000;
11
+
12
+ function ensureDir() {
13
+ if (!fs.existsSync(DATA_DIR)) {
14
+ fs.mkdirSync(DATA_DIR, { recursive: true });
15
+ }
16
+ }
17
+
18
+ function loadLearnings() {
19
+ ensureDir();
20
+ if (!fs.existsSync(LEARNINGS_FILE)) return [];
21
+ try {
22
+ const raw = fs.readFileSync(LEARNINGS_FILE, "utf8");
23
+ return JSON.parse(raw);
24
+ } catch {
25
+ return [];
26
+ }
27
+ }
28
+
29
+ function saveLearnings(learnings) {
30
+ ensureDir();
31
+ const trimmed = learnings.length > MAX_LEARNINGS ? learnings.slice(-MAX_LEARNINGS) : learnings;
32
+ fs.writeFileSync(LEARNINGS_FILE, JSON.stringify(trimmed, null, 2), "utf8");
33
+ }
34
+
35
+ export function addLearnings(entries) {
36
+ const learnings = loadLearnings();
37
+ const normalized = Array.isArray(entries) ? entries : [entries];
38
+ const withMeta = normalized.map((e) => ({
39
+ ...e,
40
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
41
+ receivedAt: new Date().toISOString(),
42
+ }));
43
+ learnings.push(...withMeta);
44
+ saveLearnings(learnings);
45
+ return withMeta.length;
46
+ }
47
+
48
+ export function getAggregatedPatterns(filters = {}) {
49
+ const learnings = loadLearnings();
50
+ const { framework, projectId, limit = 100 } = filters;
51
+
52
+ let filtered = learnings;
53
+ if (framework) {
54
+ filtered = filtered.filter((l) => (l.framework || "").toLowerCase().includes(framework.toLowerCase()));
55
+ }
56
+ if (projectId) {
57
+ filtered = filtered.filter((l) => l.projectId === projectId);
58
+ }
59
+ filtered = filtered.slice(-(limit || 500));
60
+
61
+ const byType = {};
62
+ const byFramework = {};
63
+ const byFrameworkAndType = {};
64
+ let testGenerated = 0;
65
+ let firstAttemptSuccess = 0;
66
+
67
+ filtered.forEach((l) => {
68
+ const t = l.type || "unknown";
69
+ byType[t] = (byType[t] || 0) + 1;
70
+
71
+ const f = l.framework || "unknown";
72
+ byFramework[f] = (byFramework[f] || 0) + 1;
73
+
74
+ const key = `${f}::${t}`;
75
+ byFrameworkAndType[key] = (byFrameworkAndType[key] || 0) + 1;
76
+
77
+ if (t === "test_generated") {
78
+ testGenerated++;
79
+ if (l.passedFirstTime) firstAttemptSuccess++;
80
+ }
81
+ });
82
+
83
+ const successRate = testGenerated > 0 ? Math.round((firstAttemptSuccess / testGenerated) * 100) : 0;
84
+
85
+ return {
86
+ total: filtered.length,
87
+ byType,
88
+ byFramework,
89
+ byFrameworkAndType,
90
+ testGenerated,
91
+ firstAttemptSuccessRate: successRate,
92
+ recommendations: buildRecommendations(byType, successRate),
93
+ };
94
+ }
95
+
96
+ function buildRecommendations(byType, successRate) {
97
+ const recs = [];
98
+ if (byType.element_not_rendered > 0 || byType.element_not_visible > 0) {
99
+ recs.push("Use waits explícitos (waitForSelector, waitForDisplayed) antes de interagir com elementos.");
100
+ }
101
+ if (byType.timing_fix > 0 || byType.element_stale > 0) {
102
+ recs.push("Aumente timeouts e use re-localização de elementos em listas dinâmicas.");
103
+ }
104
+ if (byType.selector_fix > 0 || byType.mobile_mapping_invisible > 0) {
105
+ recs.push("Priorize data-testid, role e seletores estáveis; em mobile, use mapeamento visível.");
106
+ }
107
+ if (successRate < 70) {
108
+ recs.push("Taxa de sucesso baixa: aplique waits inteligentes + assert final em cada teste.");
109
+ }
110
+ if (recs.length === 0) {
111
+ recs.push("Continue gerando testes. O agente está evoluindo bem.");
112
+ }
113
+ return recs;
114
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-lab-agent",
3
- "version": "2.1.3",
4
- "description": "Autonomous QA agent: executor + intelligent consultant - analyzes, predicts, recommends and learns (modular)",
3
+ "version": "2.1.6",
4
+ "description": "Sistema de Inteligência em Qualidade de Software: executa, analisa, prevê, recomenda e aprende. Memória local + Learning Hub.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
+ "learning-hub",
12
13
  "slack-bot",
13
14
  "qa-lab-agent.config.json",
14
15
  "qa-lab-flows.json.example"
@@ -17,6 +18,8 @@
17
18
  "build": "tsup",
18
19
  "dev": "tsup --watch",
19
20
  "slack-bot": "node slack-bot/src/index.js",
21
+ "slack-bot:check": "node slack-bot/check-config.js",
22
+ "learning-hub": "node learning-hub/src/server.js",
20
23
  "test": "vitest run",
21
24
  "test:watch": "vitest",
22
25
  "test:coverage": "vitest run --coverage",
@@ -42,7 +45,7 @@
42
45
  ],
43
46
  "repository": {
44
47
  "type": "git",
45
- "url": "https://github.com/Wesley-Gomes93/mcp-lab-agent"
48
+ "url": "git+https://github.com/Wesley-Gomes93/mcp-lab-agent.git"
46
49
  },
47
50
  "author": "Wesley Gomes",
48
51
  "license": "MIT",
@@ -51,6 +54,7 @@
51
54
  },
52
55
  "dependencies": {
53
56
  "@modelcontextprotocol/sdk": "^1.27.1",
57
+ "@slack/bolt": "^3.21.0",
54
58
  "dotenv": "^16.4.5",
55
59
  "zod": "^3.25.0"
56
60
  },
@@ -1,6 +1,18 @@
1
- # === OBRIGATÓRIO (pegue em api.slack.com/apps) ===
1
+ # === CREDENCIAIS (api.slack.com/apps → sua app) ===
2
+ # Doc: https://docs.slack.dev/app-management/quickstart-app-settings
3
+
4
+ # OBRIGATÓRIO: Bot User OAuth Token
5
+ # Onde: OAuth & Permissions → OAuth Tokens → Bot User OAuth Token
2
6
  SLACK_BOT_TOKEN=xoxb-...
3
- SLACK_SIGNING_SECRET=...
7
+
8
+ # Escolha UM modo:
9
+ # A) Socket Mode — recomendado (PC corporativo, sem URL pública)
10
+ # Onde: Basic Information → App-Level Tokens → Generate (scope: connections:write)
11
+ SLACK_APP_TOKEN=xapp-...
12
+
13
+ # B) HTTP — precisa de ngrok
14
+ # Onde: Basic Information → App Credentials → Signing Secret (Show)
15
+ # SLACK_SIGNING_SECRET=...
4
16
 
5
17
  # === REPO (se não configurou no qa-lab-agent.config.json) ===
6
18
  # REPO_URL=https://github.com/sua-empresa/projeto.git
@@ -8,3 +20,6 @@ SLACK_SIGNING_SECRET=...
8
20
 
9
21
  # === LLM (opcional - só para "crie testes") ===
10
22
  # GROQ_API_KEY=...
23
+
24
+ # === AVANÇADO: caminho customizado do .env ===
25
+ # QA_LAB_ENV=/caminho/para/.env
@@ -0,0 +1,23 @@
1
+ # Credenciais — QA Lab Slack Bot
2
+
3
+ Referência rápida de onde obter cada credencial em [api.slack.com/apps](https://api.slack.com/apps).
4
+
5
+ Baseado em [Creating an app from app settings](https://docs.slack.dev/app-management/quickstart-app-settings).
6
+
7
+ ## Tabela de credenciais
8
+
9
+ | Credencial | Chave no mcp.json | Chave no .env | Onde obter em api.slack.com |
10
+ |-----------------|-------------------|---------------------------|-----------------------------|
11
+ | Bot User OAuth | `botToken` | `SLACK_BOT_TOKEN` | OAuth & Permissions → OAuth Tokens → Bot User OAuth Token |
12
+ | App-Level Token | `appToken` | `SLACK_APP_TOKEN` | Basic Information → App-Level Tokens → Generate (scope: `connections:write`) |
13
+ | Signing Secret | `signingSecret` | `SLACK_SIGNING_SECRET` | Basic Information → App Credentials → Signing Secret (Show) |
14
+
15
+ ## Formato dos tokens
16
+
17
+ - **Bot Token:** começa com `xoxb-` (não use Client Secret nem User Token)
18
+ - **App Token:** começa com `xapp-` (só para Socket Mode)
19
+ - **Signing Secret:** string longa (HTTP mode)
20
+
21
+ ## Segurança
22
+
23
+ Mantenha as credenciais em segredo. Use variáveis de ambiente ou `~/.cursor/mcp.json` fora do versionamento.
@@ -2,31 +2,100 @@
2
2
 
3
3
  Bot Slack que analisa projetos e gera testes via **mcp-lab-agent**.
4
4
 
5
- ## Configuração (3 passos)
5
+ **Funciona em qualquer ambiente** — pessoal, corporativo, com ou sem firewall. Configure uma vez e rode em qualquer máquina.
6
6
 
7
- ### 1. Crie o bot no Slack
7
+ | Ambiente | Modo recomendado | URL pública? |
8
+ |-----------------|------------------|------------------|
9
+ | PC corporativo | Socket Mode | Não precisa |
10
+ | PC pessoal | Socket ou HTTP | Só se usar HTTP |
11
+ | CI / servidor | Socket ou HTTP | Conforme rede |
8
12
 
9
- 1. [api.slack.com/apps](https://api.slack.com/apps) **Create New App** → **From scratch**
10
- 2. **OAuth & Permissions** → Scopes: `app_mentions:read`, `chat:write`, `channels:history`, `channels:read`
11
- 3. **Event Subscriptions** → Enable → URL: `https://SEU_DOMINIO/slack/events` → event: `app_mention`
12
- 4. **Install to Workspace** → copie o token (`xoxb-...`)
13
- 5. **Basic Information** → copie o **Signing Secret**
14
-
15
- ### 2. Configure o .env
13
+ ## Rodar sem clonar o projeto
16
14
 
17
15
  ```bash
18
- cd slack-bot
19
- cp .env.example .env
16
+ npx mcp-lab-agent slack-bot
20
17
  ```
21
18
 
22
- Edite `.env` e preencha:
19
+ Não precisa baixar o repo — o comando instala e executa o bot. Configure `~/.cursor/mcp.json` (ver seção abaixo).
23
20
 
21
+ ## Configuração
22
+
23
+ Seguindo [Creating an app from app settings](https://docs.slack.dev/app-management/quickstart-app-settings):
24
+
25
+ ### 1. Criar o app no Slack
26
+
27
+ 1. [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch**
28
+ 2. **App Name** e **Workspace** → **Create App**
29
+
30
+ ### 2. Solicitar scopes (OAuth & Permissions)
31
+
32
+ 1. **OAuth & Permissions** → **Scopes** → **Bot Token Scopes** → **Add an OAuth Scope**
33
+ 2. Adicione:
34
+ - `app_mentions:read` — necessário para o evento `app_mention`
35
+ - `chat:write` — enviar mensagens
36
+ - `channels:read` — listar canais
37
+ - `channels:history` — ler histórico (para análise)
38
+
39
+ ### 3. Instalar e autorizar o app
40
+
41
+ 1. **OAuth & Permissions** → **Install to Workspace** → **Allow**
42
+ 2. Copie o **Bot User OAuth Token** (`xoxb-...`) em **OAuth Tokens**
43
+ 3. No canal desejado, digite: `/invite @NomeDoBot` — o bot precisa estar no canal para receber menções
44
+
45
+ ### 4. Configurar eventos (Event Subscriptions)
46
+
47
+ 1. **Event Subscriptions** → **Enable Events** → ON
48
+ 2. **Subscribe to bot events** → **Add Bot User Event** → `app_mention`
49
+ 3. Se alterou scopes/eventos: **Install App** → **Reinstall to Workspace**
50
+
51
+ ### 5. Escolher modo: Socket ou HTTP
52
+
53
+ **A) Socket Mode** (recomendado — PC corporativo, sem URL pública):
54
+
55
+ 1. **Socket Mode** → **Enable Socket Mode** → ON
56
+ 2. **Basic Information** → **App-Level Tokens** → **Generate** → scope: `connections:write`
57
+ 3. Copie o token (`xapp-...`)
58
+
59
+ **B) HTTP** (ngrok):
60
+
61
+ 1. **Event Subscriptions** → **Request URL**: `https://SEU_DOMINIO/slack/events`
62
+ 2. **Basic Information** → **App Credentials** → **Signing Secret** (Show)
63
+
64
+ ### 6. Configurar credenciais localmente
65
+
66
+ **Socket Mode** — em `~/.cursor/mcp.json` ou `.env`:
67
+
68
+ ```json
69
+ {
70
+ "qa-lab-agent": {
71
+ "slack": {
72
+ "botToken": "xoxb-...",
73
+ "appToken": "xapp-...",
74
+ "useLocal": true
75
+ }
76
+ }
77
+ }
24
78
  ```
25
- SLACK_BOT_TOKEN=xoxb-seu-token
26
- SLACK_SIGNING_SECRET=seu-secret
27
- ```
28
79
 
29
- ### 3. Configure o repositório
80
+ | Credencial | Onde obter em api.slack.com |
81
+ |-------------|-----------------------------|
82
+ | `botToken` | OAuth & Permissions → OAuth Tokens → Bot User OAuth Token |
83
+ | `appToken` | Basic Information → App-Level Tokens (scope: connections:write) |
84
+
85
+ > Detalhes: [CREDENTIALS.md](./CREDENTIALS.md)
86
+
87
+ **HTTP** — para ngrok:
88
+
89
+ | Credencial | Onde obter em api.slack.com |
90
+ |-----------------|-----------------------------|
91
+ | `botToken` | OAuth & Permissions → OAuth Tokens → Bot User OAuth Token |
92
+ | `signingSecret` | Basic Information → App Credentials → Signing Secret |
93
+
94
+ **Via `.env`:** `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` (Socket) ou `SLACK_SIGNING_SECRET` (HTTP). O `.env` pode ficar em `slack-bot/` ou na pasta atual.
95
+
96
+ > **Importante:** Mantenha as credenciais em segredo. Use variáveis de ambiente ou `mcp.json` (fora do versionamento).
97
+
98
+ ### 7. Configure o repositório
30
99
 
31
100
  **Opção A** — No `qa-lab-agent.config.json` (raiz do projeto):
32
101
 
@@ -64,3 +133,5 @@ Local com ngrok: `ngrok http 3000` e use a URL em Event Subscriptions.
64
133
  @QA Lab Bot analise o projeto
65
134
  @QA Lab Bot crie testes para login
66
135
  ```
136
+
137
+ **Bot não responde?** Veja [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) — em geral: `/invite @NomeDoBot` no canal e conferir **Subscribe to bot events** → `app_mention`.