deepdebug-local-agent 0.3.1 → 0.3.3

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.
package/.dockerignore CHANGED
@@ -20,5 +20,6 @@ __tests__
20
20
  .idea/deepdebug-local-agent.iml
21
21
  .idea/modules.xml
22
22
  node_modules/
23
+ package-lock.json
23
24
  src/.DS_Store
24
25
  src/analyzers/
package/bin/install.js ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * deepdebug-local-agent CLI
5
+ *
6
+ * Usage:
7
+ * npx deepdebug-local-agent install — installs and starts the agent as a service
8
+ * npx deepdebug-local-agent start — starts the agent
9
+ * npx deepdebug-local-agent stop — stops the agent
10
+ * npx deepdebug-local-agent status — shows agent status
11
+ * npx deepdebug-local-agent run — runs the agent in the foreground (dev mode)
12
+ */
13
+
14
+ import { execSync, spawn } from 'child_process';
15
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
16
+ import { join, dirname } from 'path';
17
+ import { fileURLToPath } from 'url';
18
+ import os from 'os';
19
+ import readline from 'readline';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
23
+ const ROOT_DIR = join(__dirname, '..');
24
+
25
+ const CONFIG_DIR = join(os.homedir(), '.deepdebug');
26
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
27
+ const DEFAULT_PORT = 5055;
28
+ const DEFAULT_GATEWAY = 'https://gateway.insptech.pt';
29
+ const AGENT_NAME = 'deepdebug-agent';
30
+
31
+ const BLUE = '\x1b[34m';
32
+ const GREEN = '\x1b[32m';
33
+ const YELLOW = '\x1b[33m';
34
+ const RED = '\x1b[31m';
35
+ const BOLD = '\x1b[1m';
36
+ const RESET = '\x1b[0m';
37
+
38
+ const log = (msg) => console.log(`${BLUE}[DeepDebug]${RESET} ${msg}`);
39
+ const ok = (msg) => console.log(`${GREEN}✓${RESET} ${msg}`);
40
+ const warn = (msg) => console.log(`${YELLOW}⚠${RESET} ${msg}`);
41
+ const err = (msg) => console.log(`${RED}✗${RESET} ${msg}`);
42
+ const bold = (msg) => `${BOLD}${msg}${RESET}`;
43
+
44
+ function printBanner() {
45
+ console.log('');
46
+ console.log(`${BLUE}${BOLD}╔═══════════════════════════════════════╗${RESET}`);
47
+ console.log(`${BLUE}${BOLD}║ Insptech AI — DeepDebug Agent ║${RESET}`);
48
+ console.log(`${BLUE}${BOLD}║ deepdebug-local-agent ║${RESET}`);
49
+ console.log(`${BLUE}${BOLD}╚═══════════════════════════════════════╝${RESET}`);
50
+ console.log('');
51
+ }
52
+
53
+ function ask(question) {
54
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
55
+ return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
56
+ }
57
+
58
+ function loadConfig() {
59
+ try {
60
+ if (existsSync(CONFIG_FILE)) {
61
+ return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
62
+ }
63
+ } catch {}
64
+ return {};
65
+ }
66
+
67
+ function saveConfig(cfg) {
68
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
69
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf8');
70
+ }
71
+
72
+ function checkNodeVersion() {
73
+ const major = parseInt(process.version.slice(1).split('.')[0]);
74
+ if (major < 18) {
75
+ err(`Node.js ${process.version} detected. Version 18 or higher is required.`);
76
+ err('Download: https://nodejs.org');
77
+ process.exit(1);
78
+ }
79
+ ok(`Node.js ${process.version} detected`);
80
+ }
81
+
82
+ function getServerScript() {
83
+ // Try common locations
84
+ const candidates = [
85
+ join(ROOT_DIR, 'server.js'),
86
+ join(ROOT_DIR, 'src', 'server.js'),
87
+ ];
88
+ for (const c of candidates) {
89
+ if (existsSync(c)) return c;
90
+ }
91
+ // Fallback: server.js next to bin/
92
+ return join(ROOT_DIR, 'server.js');
93
+ }
94
+
95
+ // ─── Platform detection ───────────────────────────────────────────────────────
96
+
97
+ const platform = os.platform(); // 'win32' | 'darwin' | 'linux'
98
+
99
+ function getPidFile() {
100
+ return join(CONFIG_DIR, 'agent.pid');
101
+ }
102
+
103
+ function readPid() {
104
+ try {
105
+ const pid = parseInt(readFileSync(getPidFile(), 'utf8').trim());
106
+ return isNaN(pid) ? null : pid;
107
+ } catch { return null; }
108
+ }
109
+
110
+ function isRunning(pid) {
111
+ try { process.kill(pid, 0); return true; } catch { return false; }
112
+ }
113
+
114
+ // ─── Commands ─────────────────────────────────────────────────────────────────
115
+
116
+ async function cmdInstall() {
117
+ printBanner();
118
+ log('Starting installation...');
119
+ console.log('');
120
+
121
+ checkNodeVersion();
122
+
123
+ // Read or create config
124
+ let cfg = loadConfig();
125
+
126
+ // Prompt for API Key if not set
127
+ if (!cfg.apiKey) {
128
+ console.log(`${YELLOW}You need an API Key from the Insptech AI platform.${RESET}`);
129
+ console.log(`Open ${bold('https://app.insptech.pt')} → Settings → Agent → Generate API Key`);
130
+ console.log('');
131
+ const key = await ask(' Enter your API Key: ');
132
+ if (!key || key.length < 10) {
133
+ err('Invalid API Key. Installation aborted.');
134
+ process.exit(1);
135
+ }
136
+ cfg.apiKey = key;
137
+ } else {
138
+ ok('API Key already configured');
139
+ }
140
+
141
+ if (!cfg.gatewayUrl) cfg.gatewayUrl = DEFAULT_GATEWAY;
142
+ if (!cfg.port) cfg.port = DEFAULT_PORT;
143
+
144
+ saveConfig(cfg);
145
+ ok(`Configuration saved to ${CONFIG_FILE}`);
146
+
147
+ // Install npm dependencies if needed
148
+ const nodeModules = join(ROOT_DIR, 'node_modules');
149
+ if (!existsSync(nodeModules)) {
150
+ log('Installing dependencies...');
151
+ try {
152
+ execSync('npm install --production', { cwd: ROOT_DIR, stdio: 'inherit' });
153
+ ok('Dependencies installed');
154
+ } catch (e) {
155
+ warn('Could not install dependencies automatically. Run: npm install');
156
+ }
157
+ } else {
158
+ ok('Dependencies already installed');
159
+ }
160
+
161
+ console.log('');
162
+
163
+ // Start the agent
164
+ await cmdStart(cfg);
165
+
166
+ console.log('');
167
+ ok(bold('Agent registered successfully'));
168
+ console.log('');
169
+ log(`Health check: ${bold(`http://localhost:${cfg.port}/health`)}`);
170
+ log(`Platform: ${bold('https://app.insptech.pt')}`);
171
+ console.log('');
172
+ }
173
+
174
+ async function cmdStart(cfg) {
175
+ cfg = cfg || loadConfig();
176
+
177
+ const pid = readPid();
178
+ if (pid && isRunning(pid)) {
179
+ ok(`Agent is already running (PID ${pid})`);
180
+ return;
181
+ }
182
+
183
+ const serverScript = getServerScript();
184
+ if (!existsSync(serverScript)) {
185
+ err(`Server script not found: ${serverScript}`);
186
+ process.exit(1);
187
+ }
188
+
189
+ log(`Starting agent on port ${cfg.port || DEFAULT_PORT}...`);
190
+
191
+ const env = {
192
+ ...process.env,
193
+ NODE_ENV: 'production',
194
+ PORT: String(cfg.port || DEFAULT_PORT),
195
+ DEEPDEBUG_API_KEY: cfg.apiKey || '',
196
+ GATEWAY_URL: cfg.gatewayUrl || DEFAULT_GATEWAY,
197
+ };
198
+
199
+ const child = spawn(process.execPath, [serverScript], {
200
+ env,
201
+ detached: true,
202
+ stdio: 'ignore',
203
+ cwd: ROOT_DIR,
204
+ });
205
+
206
+ child.unref();
207
+
208
+ // Save PID
209
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
210
+ writeFileSync(getPidFile(), String(child.pid), 'utf8');
211
+
212
+ // Wait a moment and verify
213
+ await new Promise(r => setTimeout(r, 1500));
214
+
215
+ if (isRunning(child.pid)) {
216
+ ok(`Agent started (PID ${child.pid})`);
217
+ } else {
218
+ warn('Agent may have failed to start. Check logs or run in foreground: npx deepdebug-local-agent run');
219
+ }
220
+ }
221
+
222
+ function cmdStop() {
223
+ const pid = readPid();
224
+ if (!pid) { warn('No PID file found. Agent may not be running.'); return; }
225
+ if (!isRunning(pid)) { warn(`No process with PID ${pid}. Agent is not running.`); return; }
226
+ try {
227
+ process.kill(pid, 'SIGTERM');
228
+ ok(`Agent stopped (PID ${pid})`);
229
+ } catch (e) {
230
+ err(`Failed to stop agent: ${e.message}`);
231
+ }
232
+ }
233
+
234
+ function cmdStatus() {
235
+ const cfg = loadConfig();
236
+ const pid = readPid();
237
+ const port = cfg.port || DEFAULT_PORT;
238
+ console.log('');
239
+ console.log(`${BOLD}DeepDebug Local Agent — Status${RESET}`);
240
+ console.log('─'.repeat(40));
241
+ if (pid && isRunning(pid)) {
242
+ console.log(` Process : ${GREEN}Running${RESET} (PID ${pid})`);
243
+ } else {
244
+ console.log(` Process : ${RED}Not running${RESET}`);
245
+ }
246
+ console.log(` Port : ${port}`);
247
+ console.log(` Config : ${CONFIG_FILE}`);
248
+ console.log(` Gateway : ${cfg.gatewayUrl || DEFAULT_GATEWAY}`);
249
+ console.log(` Health : http://localhost:${port}/health`);
250
+ console.log('');
251
+ }
252
+
253
+ function cmdRun() {
254
+ // Foreground mode — useful for debugging
255
+ const cfg = loadConfig();
256
+ const serverScript = getServerScript();
257
+ const env = {
258
+ ...process.env,
259
+ NODE_ENV: 'development',
260
+ PORT: String(cfg.port || DEFAULT_PORT),
261
+ DEEPDEBUG_API_KEY: cfg.apiKey || '',
262
+ GATEWAY_URL: cfg.gatewayUrl || DEFAULT_GATEWAY,
263
+ };
264
+ log(`Running in foreground on port ${cfg.port || DEFAULT_PORT} (Ctrl+C to stop)...`);
265
+ const child = spawn(process.execPath, [serverScript], { env, stdio: 'inherit', cwd: ROOT_DIR });
266
+ child.on('exit', code => process.exit(code || 0));
267
+ }
268
+
269
+ // ─── Entry point ──────────────────────────────────────────────────────────────
270
+
271
+ const command = process.argv[2] || 'install';
272
+
273
+ switch (command) {
274
+ case 'install': cmdInstall().catch(e => { err(e.message); process.exit(1); }); break;
275
+ case 'start': cmdStart().catch(e => { err(e.message); process.exit(1); }); break;
276
+ case 'stop': cmdStop(); break;
277
+ case 'status': cmdStatus(); break;
278
+ case 'run': cmdRun(); break;
279
+ default:
280
+ console.log(`Usage: npx deepdebug-local-agent [install|start|stop|status|run]`);
281
+ process.exit(1);
282
+ }
package/package.json CHANGED
@@ -1,12 +1,23 @@
1
1
  {
2
2
  "name": "deepdebug-local-agent",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
+ "description": "Insptech AI — DeepDebug Local Agent. Autonomous code debugging agent for production environments.",
4
5
  "type": "module",
5
- "main": "src/server.js",
6
+ "main": "server.js",
7
+ "bin": {
8
+ "deepdebug-local-agent": "./bin/install.js"
9
+ },
6
10
  "scripts": {
7
- "start": "node src/server.js",
8
- "dev": "NODE_ENV=development node src/server.js",
9
- "mcp": "node src/mcp-server.js"
11
+ "start": "node server.js",
12
+ "dev": "NODE_ENV=development node server.js",
13
+ "mcp": "node mcp-server.js"
14
+ },
15
+ "keywords": ["deepdebug", "insptech", "debugging", "ai", "autonomous", "agent"],
16
+ "author": "Insptech AI <contacto@insptech.pt>",
17
+ "license": "UNLICENSED",
18
+ "homepage": "https://deepdebug.ai",
19
+ "engines": {
20
+ "node": ">=18.0.0"
10
21
  },
11
22
  "dependencies": {
12
23
  "@modelcontextprotocol/sdk": "^1.26.0",
@@ -19,4 +30,4 @@
19
30
  "strip-ansi": "^7.1.0",
20
31
  "unidiff": "^1.0.2"
21
32
  }
22
- }
33
+ }
@@ -22,7 +22,7 @@ export class BaseGitProvider {
22
22
  // ==========================================
23
23
 
24
24
  /**
25
- * Identificador único do provider
25
+ * Identificador único do provider
26
26
  * @returns {string} Ex: 'github', 'gitlab', 'bitbucket'
27
27
  */
28
28
  getId() {
@@ -30,7 +30,7 @@ export class BaseGitProvider {
30
30
  }
31
31
 
32
32
  /**
33
- * Nome amigável do provider
33
+ * Nome amigável do provider
34
34
  * @returns {string} Ex: 'GitHub', 'GitLab', 'Bitbucket'
35
35
  */
36
36
  getName() {
@@ -78,7 +78,7 @@ export class BaseGitProvider {
78
78
  }
79
79
 
80
80
  /**
81
- * Valida se o token ainda é válido
81
+ * Valida se o token ainda é válido
82
82
  * @returns {Promise<boolean>}
83
83
  */
84
84
  async validateToken() {
@@ -90,7 +90,7 @@ export class BaseGitProvider {
90
90
  // ==========================================
91
91
 
92
92
  /**
93
- * Obtém informações do usuário autenticado
93
+ * Obtém informações do usuário autenticado
94
94
  * @returns {Promise<UserInfo>}
95
95
  */
96
96
  async getCurrentUser() {
@@ -102,7 +102,7 @@ export class BaseGitProvider {
102
102
  // ==========================================
103
103
 
104
104
  /**
105
- * Lista repositórios do usuário
105
+ * Lista repositórios do usuário
106
106
  * @param {object} options - { page, perPage, sort }
107
107
  * @returns {Promise<Repository[]>}
108
108
  */
@@ -111,7 +111,7 @@ export class BaseGitProvider {
111
111
  }
112
112
 
113
113
  /**
114
- * Obtém informações de um repositório
114
+ * Obtém informações de um repositório
115
115
  * @param {string} owner - Owner do repo
116
116
  * @param {string} repo - Nome do repo
117
117
  * @returns {Promise<Repository>}
@@ -121,7 +121,7 @@ export class BaseGitProvider {
121
121
  }
122
122
 
123
123
  /**
124
- * Clona um repositório
124
+ * Clona um repositório
125
125
  * @param {string} owner - Owner do repo
126
126
  * @param {string} repo - Nome do repo
127
127
  * @param {string} destPath - Caminho de destino
@@ -137,7 +137,7 @@ export class BaseGitProvider {
137
137
  // ==========================================
138
138
 
139
139
  /**
140
- * Lista branches do repositório
140
+ * Lista branches do repositório
141
141
  * @param {string} owner - Owner do repo
142
142
  * @param {string} repo - Nome do repo
143
143
  * @returns {Promise<Branch[]>}
@@ -159,7 +159,7 @@ export class BaseGitProvider {
159
159
  }
160
160
 
161
161
  /**
162
- * Obtém informações de uma branch
162
+ * Obtém informações de uma branch
163
163
  * @param {string} owner - Owner do repo
164
164
  * @param {string} repo - Nome do repo
165
165
  * @param {string} branch - Nome da branch
@@ -186,12 +186,12 @@ export class BaseGitProvider {
186
186
  }
187
187
 
188
188
  /**
189
- * Cria um commit com mudanças
189
+ * Cria um commit com mudanças
190
190
  * @param {string} owner - Owner do repo
191
191
  * @param {string} repo - Nome do repo
192
192
  * @param {string} branch - Nome da branch
193
193
  * @param {string} message - Mensagem do commit
194
- * @param {FileChange[]} changes - Lista de mudanças
194
+ * @param {FileChange[]} changes - Lista de mudanças
195
195
  * @returns {Promise<Commit>}
196
196
  */
197
197
  async createCommit(owner, repo, branch, message, changes) {
@@ -203,7 +203,7 @@ export class BaseGitProvider {
203
203
  // ==========================================
204
204
 
205
205
  /**
206
- * Obtém conteúdo de um arquivo
206
+ * Obtém conteúdo de um arquivo
207
207
  * @param {string} owner - Owner do repo
208
208
  * @param {string} repo - Nome do repo
209
209
  * @param {string} path - Caminho do arquivo
@@ -219,7 +219,7 @@ export class BaseGitProvider {
219
219
  * @param {string} owner - Owner do repo
220
220
  * @param {string} repo - Nome do repo
221
221
  * @param {string} path - Caminho do arquivo
222
- * @param {string} content - Conteúdo (base64 ou texto)
222
+ * @param {string} content - Conteúdo (base64 ou texto)
223
223
  * @param {string} message - Mensagem do commit
224
224
  * @param {string} branch - Branch
225
225
  * @param {string} sha - SHA atual (para update)
@@ -256,16 +256,136 @@ export class BaseGitProvider {
256
256
  }
257
257
 
258
258
  /**
259
- * Obtém informações de um pull request
259
+ * Obtém informações de um pull request
260
260
  * @param {string} owner - Owner do repo
261
261
  * @param {string} repo - Nome do repo
262
- * @param {number} prNumber - Número do PR
262
+ * @param {number} prNumber - Número do PR
263
263
  * @returns {Promise<PullRequest>}
264
264
  */
265
265
  async getPullRequest(owner, repo, prNumber) {
266
266
  throw new Error('Method getPullRequest() must be implemented');
267
267
  }
268
268
 
269
+ // ==========================================
270
+ // PULL REQUEST COMMENTS / REVIEWS
271
+ // ==========================================
272
+
273
+ /**
274
+ * Lista comentários de um pull request
275
+ * @param {string} owner - Owner do repo
276
+ * @param {string} repo - Nome do repo
277
+ * @param {number} prNumber - Número do PR
278
+ * @param {object} options - { page, perPage }
279
+ * @returns {Promise<PRComment[]>}
280
+ */
281
+ async listPRComments(owner, repo, prNumber, options = {}) {
282
+ throw new Error('Method listPRComments() must be implemented');
283
+ }
284
+
285
+ /**
286
+ * Lista apenas comentários de review (inline no código)
287
+ * @param {string} owner - Owner do repo
288
+ * @param {string} repo - Nome do repo
289
+ * @param {number} prNumber - Número do PR
290
+ * @param {object} options - { page, perPage }
291
+ * @returns {Promise<PRReviewComment[]>}
292
+ */
293
+ async listPRReviewComments(owner, repo, prNumber, options = {}) {
294
+ throw new Error('Method listPRReviewComments() must be implemented');
295
+ }
296
+
297
+ /**
298
+ * Obtém um comentário específico
299
+ * @param {string} owner - Owner do repo
300
+ * @param {string} repo - Nome do repo
301
+ * @param {number} prNumber - Número do PR
302
+ * @param {string|number} commentId - ID do comentário
303
+ * @returns {Promise<PRComment>}
304
+ */
305
+ async getPRComment(owner, repo, prNumber, commentId) {
306
+ throw new Error('Method getPRComment() must be implemented');
307
+ }
308
+
309
+ /**
310
+ * Adiciona um comentário geral ao PR
311
+ * @param {string} owner - Owner do repo
312
+ * @param {string} repo - Nome do repo
313
+ * @param {number} prNumber - Número do PR
314
+ * @param {string} body - Conteúdo do comentário
315
+ * @returns {Promise<PRComment>}
316
+ */
317
+ async addPRComment(owner, repo, prNumber, body) {
318
+ throw new Error('Method addPRComment() must be implemented');
319
+ }
320
+
321
+ /**
322
+ * Adiciona um comentário inline no código (review comment)
323
+ * @param {string} owner - Owner do repo
324
+ * @param {string} repo - Nome do repo
325
+ * @param {number} prNumber - Número do PR
326
+ * @param {object} comment - { body, path, line, side, commitId }
327
+ * @returns {Promise<PRReviewComment>}
328
+ */
329
+ async addPRReviewComment(owner, repo, prNumber, comment) {
330
+ throw new Error('Method addPRReviewComment() must be implemented');
331
+ }
332
+
333
+ /**
334
+ * Responde a um comentário existente (thread reply)
335
+ * @param {string} owner - Owner do repo
336
+ * @param {string} repo - Nome do repo
337
+ * @param {number} prNumber - Número do PR
338
+ * @param {string|number} commentId - ID do comentário pai
339
+ * @param {string} body - Conteúdo da resposta
340
+ * @returns {Promise<PRComment>}
341
+ */
342
+ async replyToPRComment(owner, repo, prNumber, commentId, body) {
343
+ throw new Error('Method replyToPRComment() must be implemented');
344
+ }
345
+
346
+ /**
347
+ * Marca um comentário/thread como resolvido
348
+ * @param {string} owner - Owner do repo
349
+ * @param {string} repo - Nome do repo
350
+ * @param {number} prNumber - Número do PR
351
+ * @param {string|number} commentId - ID do comentário/thread
352
+ * @returns {Promise<{ resolved: boolean }>}
353
+ */
354
+ async resolvePRComment(owner, repo, prNumber, commentId) {
355
+ throw new Error('Method resolvePRComment() must be implemented');
356
+ }
357
+
358
+ /**
359
+ * Marca um comentário/thread como não resolvido
360
+ * @param {string} owner - Owner do repo
361
+ * @param {string} repo - Nome do repo
362
+ * @param {number} prNumber - Número do PR
363
+ * @param {string|number} commentId - ID do comentário/thread
364
+ * @returns {Promise<{ resolved: boolean }>}
365
+ */
366
+ async unresolvePRComment(owner, repo, prNumber, commentId) {
367
+ throw new Error('Method unresolvePRComment() must be implemented');
368
+ }
369
+
370
+ /**
371
+ * Lista comentários não resolvidos de um PR
372
+ * Convenience method - filtra listPRComments + listPRReviewComments
373
+ * @param {string} owner - Owner do repo
374
+ * @param {string} repo - Nome do repo
375
+ * @param {number} prNumber - Número do PR
376
+ * @returns {Promise<PRComment[]>}
377
+ */
378
+ async listUnresolvedPRComments(owner, repo, prNumber) {
379
+ // Default implementation: subclasses can override for better performance
380
+ const [general, review] = await Promise.all([
381
+ this.listPRComments(owner, repo, prNumber).catch(() => []),
382
+ this.listPRReviewComments(owner, repo, prNumber).catch(() => [])
383
+ ]);
384
+
385
+ const all = [...general, ...review];
386
+ return all.filter(c => c.resolved === false || c.resolved === undefined);
387
+ }
388
+
269
389
  // ==========================================
270
390
  // WEBHOOKS
271
391
  // ==========================================
@@ -286,7 +406,7 @@ export class BaseGitProvider {
286
406
  // ==========================================
287
407
 
288
408
  /**
289
- * Constrói URL de clone
409
+ * Constrói URL de clone
290
410
  * @param {string} owner - Owner do repo
291
411
  * @param {string} repo - Nome do repo
292
412
  * @param {boolean} useHttps - Usar HTTPS (default) ou SSH
@@ -297,8 +417,8 @@ export class BaseGitProvider {
297
417
  }
298
418
 
299
419
  /**
300
- * Parsea URL de repositório para extrair owner e repo
301
- * @param {string} url - URL do repositório
420
+ * Parsea URL de repositório para extrair owner e repo
421
+ * @param {string} url - URL do repositório
302
422
  * @returns {{ owner: string, repo: string }}
303
423
  */
304
424
  parseRepositoryUrl(url) {
@@ -381,4 +501,35 @@ export class BaseGitProvider {
381
501
  * @property {string} htmlUrl
382
502
  */
383
503
 
504
+ /**
505
+ * @typedef {object} PRComment
506
+ * @property {string|number} id
507
+ * @property {string} body
508
+ * @property {string} authorUsername
509
+ * @property {string} authorName
510
+ * @property {string} createdAt
511
+ * @property {string} updatedAt
512
+ * @property {string} htmlUrl
513
+ * @property {boolean} [resolved]
514
+ * @property {string} [type] - 'general' | 'review' | 'inline'
515
+ */
516
+
517
+ /**
518
+ * @typedef {object} PRReviewComment
519
+ * @property {string|number} id
520
+ * @property {string} body
521
+ * @property {string} path - File path the comment is on
522
+ * @property {number} [line] - Line number
523
+ * @property {string} [side] - 'LEFT' | 'RIGHT'
524
+ * @property {string} [commitId] - Commit SHA the comment refers to
525
+ * @property {string} authorUsername
526
+ * @property {string} authorName
527
+ * @property {string} createdAt
528
+ * @property {string} updatedAt
529
+ * @property {string} htmlUrl
530
+ * @property {boolean} [resolved]
531
+ * @property {string|number} [threadId] - Thread/conversation ID for grouping
532
+ * @property {string|number} [inReplyToId] - Parent comment ID if this is a reply
533
+ */
534
+
384
535
  export default BaseGitProvider;