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 +1 -0
- package/bin/install.js +282 -0
- package/package.json +17 -6
- package/src/git/base-git-provider.js +169 -18
- package/src/git/bitbucket-provider.js +723 -0
- package/src/git/git-provider-registry.js +12 -11
- package/src/git/github-provider.js +299 -6
- package/src/server.js +587 -42
package/.dockerignore
CHANGED
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.
|
|
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": "
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"deepdebug-local-agent": "./bin/install.js"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
|
-
"start": "node
|
|
8
|
-
"dev": "NODE_ENV=development node
|
|
9
|
-
"mcp": "node
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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 -
|
|
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
|
-
*
|
|
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 -
|
|
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
|
-
*
|
|
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
|
|
301
|
-
* @param {string} url - URL do
|
|
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;
|