memory-journal-mcp 7.4.0 → 7.6.0

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.
@@ -1,1745 +0,0 @@
1
- import { Octokit } from '@octokit/rest';
2
- import { graphql } from '@octokit/graphql';
3
- import * as simpleGitImport from 'simple-git';
4
- import { execFileSync } from 'child_process';
5
-
6
- // src/github/github-integration/client.ts
7
-
8
- // src/utils/errors/suggestions.ts
9
- var GENERIC_CODES = /* @__PURE__ */ new Set(["QUERY_FAILED", "INTERNAL_ERROR", "UNKNOWN_ERROR"]);
10
- var ERROR_SUGGESTIONS = [
11
- // Resource not found patterns
12
- {
13
- pattern: /not found/i,
14
- suggestion: "Verify the resource identifier and try again",
15
- code: "RESOURCE_NOT_FOUND"
16
- },
17
- {
18
- pattern: /no such table/i,
19
- suggestion: "The database table does not exist. Run database initialization.",
20
- code: "TABLE_NOT_FOUND"
21
- },
22
- // Permission / access patterns
23
- {
24
- pattern: /permission denied|SQLITE_READONLY/i,
25
- suggestion: "Check file permissions and database access rights",
26
- code: "PERMISSION_DENIED"
27
- },
28
- // Connection / lock patterns
29
- {
30
- pattern: /database is locked/i,
31
- suggestion: "The database is locked by another process. Retry after a short delay.",
32
- code: "CONNECTION_FAILED"
33
- },
34
- // Constraint patterns
35
- {
36
- pattern: /SQLITE_CONSTRAINT|unique constraint/i,
37
- suggestion: "A uniqueness or integrity constraint was violated. Check input values.",
38
- code: "VALIDATION_ERROR"
39
- },
40
- // Disk / space patterns
41
- {
42
- pattern: /disk I\/O|SQLITE_FULL/i,
43
- suggestion: "The disk may be full or the filesystem is read-only."
44
- },
45
- // Malformed input patterns
46
- {
47
- pattern: /malformed|invalid json|unexpected token/i,
48
- suggestion: "The input appears malformed. Check the format and try again.",
49
- code: "VALIDATION_ERROR"
50
- },
51
- // Schema / types patterns
52
- {
53
- pattern: /invalid input syntax for type|requires a.*column/i,
54
- suggestion: "The provided value is not valid for the assigned type.",
55
- code: "VALIDATION_ERROR"
56
- },
57
- {
58
- pattern: /^Missing required parameters:/i,
59
- suggestion: "Provide all required parameters in your request.",
60
- code: "VALIDATION_ERROR"
61
- },
62
- // Codemode / Sandbox patterns
63
- {
64
- pattern: /execution timed out/i,
65
- suggestion: "Reduce code complexity or increase timeout (max 30s). Break into smaller operations.",
66
- code: "QUERY_FAILED"
67
- },
68
- {
69
- pattern: /code validation failed/i,
70
- suggestion: "Check for blocked patterns. Use mj.* API instead.",
71
- code: "VALIDATION_ERROR"
72
- },
73
- {
74
- pattern: /sandbox.*not initialized/i,
75
- suggestion: "Internal sandbox error. Retry the operation.",
76
- code: "INTERNAL_ERROR"
77
- }
78
- ];
79
- function matchSuggestion(message) {
80
- for (const entry of ERROR_SUGGESTIONS) {
81
- if (entry.pattern.test(message)) {
82
- return { suggestion: entry.suggestion, code: entry.code };
83
- }
84
- }
85
- return void 0;
86
- }
87
-
88
- // src/types/errors.ts
89
- var MemoryJournalMcpError = class extends Error {
90
- /** Module-prefixed error code */
91
- code;
92
- /** Error category for programmatic handling */
93
- category;
94
- /** Actionable suggestion for resolving the error */
95
- suggestion;
96
- /** Whether the operation can be retried */
97
- recoverable;
98
- /** Additional structured context */
99
- details;
100
- constructor(message, code, category, options) {
101
- super(message, options?.cause ? { cause: options.cause } : void 0);
102
- this.name = "MemoryJournalMcpError";
103
- this.category = category;
104
- this.recoverable = options?.recoverable ?? false;
105
- this.details = options?.details;
106
- const matched = matchSuggestion(message);
107
- if (GENERIC_CODES.has(code) && matched?.code) {
108
- this.code = matched.code;
109
- } else {
110
- this.code = code;
111
- }
112
- this.suggestion = options?.suggestion ?? matched?.suggestion;
113
- }
114
- /**
115
- * Convert to a structured ErrorResponse for tool responses.
116
- */
117
- toResponse() {
118
- return {
119
- success: false,
120
- error: this.message,
121
- code: this.code,
122
- category: this.category,
123
- suggestion: this.suggestion,
124
- recoverable: this.recoverable,
125
- ...this.details ? { details: this.details } : {}
126
- };
127
- }
128
- };
129
- var ConnectionError = class extends MemoryJournalMcpError {
130
- constructor(message, details) {
131
- super(message, "CONNECTION_FAILED", "connection" /* CONNECTION */, {
132
- suggestion: "Check database path and file permissions",
133
- recoverable: true,
134
- details
135
- });
136
- this.name = "ConnectionError";
137
- }
138
- };
139
- var QueryError = class extends MemoryJournalMcpError {
140
- constructor(message, details) {
141
- super(message, "QUERY_FAILED", "query" /* QUERY */, {
142
- suggestion: "Check query parameters and database state",
143
- recoverable: false,
144
- details
145
- });
146
- this.name = "QueryError";
147
- }
148
- };
149
- var ValidationError = class extends MemoryJournalMcpError {
150
- constructor(message, details) {
151
- super(message, "VALIDATION_ERROR", "validation" /* VALIDATION */, {
152
- suggestion: "Check input parameters against the tool schema",
153
- recoverable: false,
154
- details
155
- });
156
- this.name = "ValidationError";
157
- }
158
- };
159
- var ResourceNotFoundError = class extends MemoryJournalMcpError {
160
- constructor(resourceType, identifier) {
161
- super(
162
- `${resourceType} not found: ${identifier}`,
163
- "RESOURCE_NOT_FOUND",
164
- "resource" /* RESOURCE */,
165
- {
166
- suggestion: `Verify the ${resourceType.toLowerCase()} identifier and try again`,
167
- recoverable: false,
168
- details: { resourceType, identifier }
169
- }
170
- );
171
- this.name = "ResourceNotFoundError";
172
- }
173
- };
174
- var ConfigurationError = class extends MemoryJournalMcpError {
175
- constructor(message, details) {
176
- super(message, "CONFIGURATION_ERROR", "configuration" /* CONFIGURATION */, {
177
- suggestion: "Check server configuration and environment variables",
178
- recoverable: false,
179
- details
180
- });
181
- this.name = "ConfigurationError";
182
- }
183
- };
184
-
185
- // src/utils/security-utils.ts
186
- var GIT_COMMAND_TIMEOUT_MS = 3e3;
187
- var SecurityError = class extends MemoryJournalMcpError {
188
- constructor(message, code) {
189
- super(message, code, "validation" /* VALIDATION */, {
190
- suggestion: "Check input for security violations",
191
- recoverable: false
192
- });
193
- this.name = "SecurityError";
194
- }
195
- };
196
- var InvalidDateFormatError = class extends SecurityError {
197
- constructor(value) {
198
- super(`Invalid date format pattern: '${value}'`, "INVALID_DATE_FORMAT");
199
- this.name = "InvalidDateFormatError";
200
- }
201
- };
202
- var PathTraversalError = class extends SecurityError {
203
- constructor(path) {
204
- super(`Path traversal detected: '${path}'`, "PATH_TRAVERSAL");
205
- this.name = "PathTraversalError";
206
- }
207
- };
208
- var ALLOWED_DATE_FORMATS = {
209
- day: "%Y-%m-%d",
210
- week: "%Y-W%W",
211
- month: "%Y-%m"
212
- };
213
- function validateDateFormatPattern(groupBy) {
214
- const format = ALLOWED_DATE_FORMATS[groupBy];
215
- if (!format) {
216
- throw new InvalidDateFormatError(groupBy);
217
- }
218
- return format;
219
- }
220
- function assertNoPathTraversal(filename) {
221
- if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
222
- throw new PathTraversalError(filename);
223
- }
224
- }
225
- function assertSafeDirectoryPath(dirPath) {
226
- const normalized = dirPath.replace(/\\/g, "/");
227
- const segments = normalized.split("/");
228
- if (segments.some((s) => s === "..")) {
229
- throw new PathTraversalError(dirPath);
230
- }
231
- }
232
- var TOKEN_PATTERNS = [
233
- // GitHub personal access tokens (classic and fine-grained)
234
- /ghp_[A-Za-z0-9_]{36,}/g,
235
- /github_pat_[A-Za-z0-9_]{82,}/g,
236
- // Authorization headers in error dumps
237
- /Authorization:\s*(?:token|Bearer)\s+\S+/gi,
238
- // Generic Bearer tokens
239
- /Bearer\s+[A-Za-z0-9._\-~+/]+=*/gi
240
- ];
241
- function sanitizeErrorForLogging(message) {
242
- let sanitized = message;
243
- for (const pattern of TOKEN_PATTERNS) {
244
- pattern.lastIndex = 0;
245
- sanitized = sanitized.replace(pattern, "[REDACTED]");
246
- }
247
- return sanitized;
248
- }
249
- function sanitizeAuthor(raw) {
250
- return raw.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 100);
251
- }
252
- function resolveAuthor() {
253
- const envAuthor = process.env["TEAM_AUTHOR"]?.trim().replace(/"/g, "");
254
- if (envAuthor) return sanitizeAuthor(envAuthor);
255
- try {
256
- const gitUser = execFileSync("git", ["config", "user.name"], {
257
- encoding: "utf-8",
258
- timeout: GIT_COMMAND_TIMEOUT_MS
259
- }).trim().replace(/"/g, "");
260
- if (gitUser) return sanitizeAuthor(gitUser);
261
- } catch {
262
- }
263
- return "unknown";
264
- }
265
-
266
- // src/utils/logger.ts
267
- var LOG_LEVELS = {
268
- debug: 7,
269
- info: 6,
270
- notice: 5,
271
- warning: 4,
272
- error: 3,
273
- critical: 2
274
- };
275
- var Logger = class {
276
- minLevel;
277
- constructor(level = "info") {
278
- this.minLevel = LOG_LEVELS[level];
279
- }
280
- shouldLog(level) {
281
- return LOG_LEVELS[level] <= this.minLevel;
282
- }
283
- log(level, message, context) {
284
- if (!this.shouldLog(level)) return;
285
- const safeMessage = message.replace(/\n|\r/g, "");
286
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
287
- const levelUpper = level.toUpperCase().padEnd(8);
288
- const mod = context?.module ? `[${context.module.replace(/\n|\r/g, "")}]` : "";
289
- const op = context?.operation ? `[${context.operation.replace(/\n|\r/g, "")}]` : "";
290
- let line = `[${timestamp}] [${levelUpper}] ${mod}${op} ${safeMessage}`;
291
- const extras = { ...context };
292
- delete extras["module"];
293
- delete extras["operation"];
294
- if (extras["error"] != null && typeof extras["error"] === "string") {
295
- extras["error"] = sanitizeErrorForLogging(extras["error"]);
296
- }
297
- if (Object.keys(extras).length > 0) {
298
- line += ` ${JSON.stringify(extras).replace(/\n|\r/g, "")}`;
299
- }
300
- console.error(line);
301
- }
302
- debug(message, context) {
303
- this.log("debug", message, context);
304
- }
305
- info(message, context) {
306
- this.log("info", message, context);
307
- }
308
- notice(message, context) {
309
- this.log("notice", message, context);
310
- }
311
- warning(message, context) {
312
- this.log("warning", message, context);
313
- }
314
- error(message, context) {
315
- this.log("error", message, context);
316
- }
317
- critical(message, context) {
318
- this.log("critical", message, context);
319
- }
320
- setLevel(level) {
321
- if (level in LOG_LEVELS) {
322
- this.minLevel = LOG_LEVELS[level];
323
- }
324
- }
325
- };
326
- var rawLevel = process.env["LOG_LEVEL"] ?? "info";
327
- var envLevel = rawLevel in LOG_LEVELS ? rawLevel : "info";
328
- var logger = new Logger(envLevel);
329
-
330
- // src/github/github-integration/client.ts
331
- var CACHE_TTL_MS = 5 * 60 * 1e3;
332
- var TRAFFIC_CACHE_TTL_MS = 10 * 60 * 1e3;
333
- var simpleGit2 = simpleGitImport.simpleGit;
334
- var GitHubClient = class {
335
- octokit = null;
336
- graphqlWithAuth = null;
337
- git;
338
- token;
339
- cachedRepoInfo = null;
340
- apiCache = /* @__PURE__ */ new Map();
341
- constructor(workingDir = ".") {
342
- this.token = process.env["GITHUB_TOKEN"];
343
- const effectiveDir = workingDir;
344
- const resolvedDir = effectiveDir === "." ? process.cwd() : effectiveDir;
345
- logger.info("GitHub integration using directory", {
346
- module: "GitHub",
347
- workingDir,
348
- effectiveDir,
349
- resolvedDir,
350
- cwd: process.cwd()
351
- });
352
- this.git = simpleGit2(effectiveDir);
353
- if (this.token) {
354
- this.octokit = new Octokit({ auth: this.token });
355
- this.graphqlWithAuth = graphql.defaults({
356
- headers: { authorization: `token ${this.token}` }
357
- });
358
- logger.info("GitHub integration initialized with token", { module: "GitHub" });
359
- } else {
360
- logger.info("GitHub integration initialized without token (limited functionality)", {
361
- module: "GitHub"
362
- });
363
- }
364
- }
365
- isApiAvailable() {
366
- return this.octokit !== null;
367
- }
368
- getCached(key) {
369
- const entry = this.apiCache.get(key);
370
- if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {
371
- this.apiCache.delete(key);
372
- this.apiCache.set(key, entry);
373
- return entry.data;
374
- }
375
- if (entry) {
376
- this.apiCache.delete(key);
377
- }
378
- return void 0;
379
- }
380
- getCachedWithTtl(key, ttlMs) {
381
- const entry = this.apiCache.get(key);
382
- if (entry && Date.now() - entry.timestamp < ttlMs) {
383
- this.apiCache.delete(key);
384
- this.apiCache.set(key, entry);
385
- return entry.data;
386
- }
387
- if (entry) {
388
- this.apiCache.delete(key);
389
- }
390
- return void 0;
391
- }
392
- setCache(key, data) {
393
- this.apiCache.delete(key);
394
- this.apiCache.set(key, { data, timestamp: Date.now() });
395
- if (this.apiCache.size > 100) {
396
- const oldestKey = this.apiCache.keys().next().value;
397
- if (oldestKey !== void 0) {
398
- this.apiCache.delete(oldestKey);
399
- }
400
- }
401
- }
402
- invalidateCache(prefix) {
403
- for (const key of this.apiCache.keys()) {
404
- if (key.startsWith(prefix)) {
405
- this.apiCache.delete(key);
406
- }
407
- }
408
- }
409
- clearCache() {
410
- this.apiCache.clear();
411
- }
412
- };
413
-
414
- // src/github/github-integration/issues.ts
415
- var IssuesManager = class {
416
- constructor(client) {
417
- this.client = client;
418
- }
419
- client;
420
- async getIssues(owner, repo, state = "open", limit = 20) {
421
- if (!this.client.octokit) {
422
- return [];
423
- }
424
- const cacheKey = `issues:${owner}:${repo}:${state}:${String(limit)}`;
425
- const cached = this.client.getCached(cacheKey);
426
- if (cached) return cached;
427
- try {
428
- const response = await this.client.octokit.issues.listForRepo({
429
- owner,
430
- repo,
431
- state,
432
- per_page: Math.min(limit * 2, 100),
433
- sort: "updated",
434
- direction: "desc"
435
- });
436
- const result = response.data.filter((issue) => !issue.pull_request).slice(0, limit).map((issue) => ({
437
- number: issue.number,
438
- title: issue.title,
439
- url: issue.html_url,
440
- state: issue.state === "open" ? "OPEN" : "CLOSED",
441
- milestone: issue.milestone ? {
442
- number: issue.milestone.number,
443
- title: issue.milestone.title
444
- } : null
445
- }));
446
- this.client.setCache(cacheKey, result);
447
- return result;
448
- } catch (error) {
449
- logger.error("Failed to get issues", {
450
- module: "GitHub",
451
- error: error instanceof Error ? error.message : String(error)
452
- });
453
- return [];
454
- }
455
- }
456
- async getIssue(owner, repo, issueNumber) {
457
- if (!this.client.octokit) {
458
- return null;
459
- }
460
- const cacheKey = `issue:${owner}:${repo}:${String(issueNumber)}`;
461
- const cached = this.client.getCached(cacheKey);
462
- if (cached !== void 0) return cached;
463
- try {
464
- const response = await this.client.octokit.issues.get({
465
- owner,
466
- repo,
467
- issue_number: issueNumber
468
- });
469
- const issue = response.data;
470
- if (issue.pull_request) {
471
- return null;
472
- }
473
- const details = {
474
- number: issue.number,
475
- title: issue.title,
476
- url: issue.html_url,
477
- state: issue.state === "open" ? "OPEN" : "CLOSED",
478
- nodeId: issue.node_id,
479
- body: issue.body ?? null,
480
- labels: issue.labels.map((l) => typeof l === "string" ? l : l.name ?? ""),
481
- assignees: issue.assignees?.map((a) => a.login) ?? [],
482
- createdAt: issue.created_at,
483
- updatedAt: issue.updated_at,
484
- closedAt: issue.closed_at,
485
- commentsCount: issue.comments,
486
- milestone: issue.milestone ? { number: issue.milestone.number, title: issue.milestone.title } : null
487
- };
488
- this.client.setCache(cacheKey, details);
489
- return details;
490
- } catch (error) {
491
- logger.error("Failed to get issue details", {
492
- module: "GitHub",
493
- entityId: issueNumber,
494
- error: error instanceof Error ? error.message : String(error)
495
- });
496
- return null;
497
- }
498
- }
499
- async getIssueComments(owner, repo, issueNumber, limit = 30) {
500
- const _limit = Math.min(limit, 100);
501
- if (_limit <= 0) return [];
502
- if (!this.client.octokit) {
503
- return [];
504
- }
505
- const cacheKey = `issue-comments:${owner}:${repo}:${String(issueNumber)}:${String(_limit)}`;
506
- const cached = this.client.getCached(cacheKey);
507
- if (cached) return cached;
508
- try {
509
- const response = await this.client.octokit.issues.listComments({
510
- owner,
511
- repo,
512
- issue_number: issueNumber,
513
- per_page: _limit,
514
- sort: "created",
515
- direction: "asc"
516
- });
517
- const comments = response.data.slice(0, _limit).map((comment) => ({
518
- author: comment.user?.login ?? "unknown",
519
- body: comment.body ?? "",
520
- createdAt: comment.created_at
521
- }));
522
- this.client.setCache(cacheKey, comments);
523
- return comments;
524
- } catch (error) {
525
- logger.error("Failed to get issue comments", {
526
- module: "GitHub",
527
- entityId: issueNumber,
528
- error: error instanceof Error ? error.message : String(error)
529
- });
530
- return [];
531
- }
532
- }
533
- async createIssue(owner, repo, title, body, labels, assignees, milestone) {
534
- if (!this.client.octokit) {
535
- logger.error("Cannot create issue: GitHub API not available", { module: "GitHub" });
536
- return null;
537
- }
538
- try {
539
- const response = await this.client.octokit.issues.create({
540
- owner,
541
- repo,
542
- title,
543
- body,
544
- labels,
545
- assignees,
546
- milestone
547
- });
548
- logger.info("Created GitHub issue", {
549
- module: "GitHub",
550
- entityId: response.data.number,
551
- context: { title, owner, repo }
552
- });
553
- return {
554
- number: response.data.number,
555
- url: response.data.html_url,
556
- title: response.data.title,
557
- nodeId: response.data.node_id
558
- };
559
- } catch (error) {
560
- logger.error("Failed to create issue", {
561
- module: "GitHub",
562
- error: error instanceof Error ? error.message : String(error),
563
- context: { title, owner, repo }
564
- });
565
- return null;
566
- } finally {
567
- this.client.invalidateCache(`issues:${owner}:${repo}`);
568
- this.client.invalidateCache("context:");
569
- }
570
- }
571
- async closeIssue(owner, repo, issueNumber, comment) {
572
- if (!this.client.octokit) {
573
- logger.error("Cannot close issue: GitHub API not available", { module: "GitHub" });
574
- return null;
575
- }
576
- try {
577
- if (comment) {
578
- await this.client.octokit.issues.createComment({
579
- owner,
580
- repo,
581
- issue_number: issueNumber,
582
- body: comment
583
- });
584
- }
585
- const response = await this.client.octokit.issues.update({
586
- owner,
587
- repo,
588
- issue_number: issueNumber,
589
- state: "closed"
590
- });
591
- logger.info("Closed GitHub issue", {
592
- module: "GitHub",
593
- entityId: issueNumber,
594
- context: { owner, repo, hadComment: !!comment }
595
- });
596
- return {
597
- success: true,
598
- url: response.data.html_url
599
- };
600
- } catch (error) {
601
- logger.error("Failed to close issue", {
602
- module: "GitHub",
603
- entityId: issueNumber,
604
- error: error instanceof Error ? error.message : String(error)
605
- });
606
- return null;
607
- } finally {
608
- this.client.invalidateCache(`issues:${owner}:${repo}`);
609
- this.client.invalidateCache(`issue:${owner}:${repo}:${String(issueNumber)}`);
610
- this.client.invalidateCache("context:");
611
- }
612
- }
613
- };
614
-
615
- // src/github/github-integration/pull-requests.ts
616
- var PullRequestsManager = class _PullRequestsManager {
617
- constructor(client) {
618
- this.client = client;
619
- }
620
- client;
621
- /** Known Copilot bot login patterns */
622
- static COPILOT_BOT_PATTERNS = [
623
- "copilot-pull-request-reviewer[bot]",
624
- "github-copilot[bot]",
625
- "copilot[bot]"
626
- ];
627
- async getPullRequests(owner, repo, state = "open", limit = 20) {
628
- if (!this.client.octokit) {
629
- return [];
630
- }
631
- const cacheKey = `prs:${owner}:${repo}:${state}:${String(limit)}`;
632
- const cached = this.client.getCached(cacheKey);
633
- if (cached) return cached;
634
- try {
635
- const response = await this.client.octokit.pulls.list({
636
- owner,
637
- repo,
638
- state,
639
- per_page: limit,
640
- sort: "updated",
641
- direction: "desc"
642
- });
643
- const result = response.data.map((pr) => ({
644
- number: pr.number,
645
- title: pr.title,
646
- url: pr.html_url,
647
- state: pr.merged_at ? "MERGED" : pr.state === "open" ? "OPEN" : "CLOSED"
648
- }));
649
- this.client.setCache(cacheKey, result);
650
- return result;
651
- } catch (error) {
652
- logger.error("Failed to get pull requests", {
653
- module: "GitHub",
654
- error: error instanceof Error ? error.message : String(error)
655
- });
656
- return [];
657
- }
658
- }
659
- async getPullRequest(owner, repo, prNumber) {
660
- if (!this.client.octokit) {
661
- return null;
662
- }
663
- const cacheKey = `pr:${owner}:${repo}:${String(prNumber)}`;
664
- const cached = this.client.getCached(cacheKey);
665
- if (cached !== void 0) return cached;
666
- try {
667
- const response = await this.client.octokit.pulls.get({
668
- owner,
669
- repo,
670
- pull_number: prNumber
671
- });
672
- const pr = response.data;
673
- const details = {
674
- number: pr.number,
675
- title: pr.title,
676
- url: pr.html_url,
677
- state: pr.merged_at ? "MERGED" : pr.state === "open" ? "OPEN" : "CLOSED",
678
- body: pr.body,
679
- draft: pr.draft ?? false,
680
- headBranch: pr.head.ref,
681
- baseBranch: pr.base.ref,
682
- author: pr.user?.login ?? "unknown",
683
- createdAt: pr.created_at,
684
- updatedAt: pr.updated_at,
685
- mergedAt: pr.merged_at,
686
- closedAt: pr.closed_at,
687
- additions: pr.additions,
688
- deletions: pr.deletions,
689
- changedFiles: pr.changed_files
690
- };
691
- this.client.setCache(cacheKey, details);
692
- return details;
693
- } catch (error) {
694
- logger.error("Failed to get PR details", {
695
- module: "GitHub",
696
- entityId: prNumber,
697
- error: error instanceof Error ? error.message : String(error)
698
- });
699
- return null;
700
- }
701
- }
702
- static isCopilotAuthor(login) {
703
- const lower = login.toLowerCase();
704
- return _PullRequestsManager.COPILOT_BOT_PATTERNS.some(
705
- (p) => lower === p || lower.includes("copilot")
706
- );
707
- }
708
- async getReviews(owner, repo, prNumber) {
709
- if (!this.client.octokit) return [];
710
- const cacheKey = `reviews:${owner}:${repo}:${String(prNumber)}`;
711
- const cached = this.client.getCached(cacheKey);
712
- if (cached) return cached;
713
- try {
714
- const response = await this.client.octokit.rest.pulls.listReviews({
715
- owner,
716
- repo,
717
- pull_number: prNumber,
718
- per_page: 100
719
- });
720
- const reviews = response.data.map((r) => ({
721
- id: r.id,
722
- author: r.user?.login ?? "unknown",
723
- state: r.state,
724
- body: r.body ?? null,
725
- submittedAt: r.submitted_at ?? r.commit_id ?? (/* @__PURE__ */ new Date()).toISOString(),
726
- isCopilot: _PullRequestsManager.isCopilotAuthor(r.user?.login ?? "")
727
- }));
728
- this.client.setCache(cacheKey, reviews);
729
- return reviews;
730
- } catch (error) {
731
- logger.error("Failed to get PR reviews", {
732
- module: "GitHub",
733
- entityId: prNumber,
734
- error: error instanceof Error ? error.message : String(error)
735
- });
736
- return [];
737
- }
738
- }
739
- async getReviewComments(owner, repo, prNumber) {
740
- if (!this.client.octokit) return [];
741
- const cacheKey = `review-comments:${owner}:${repo}:${String(prNumber)}`;
742
- const cached = this.client.getCached(cacheKey);
743
- if (cached) return cached;
744
- try {
745
- const response = await this.client.octokit.rest.pulls.listReviewComments({
746
- owner,
747
- repo,
748
- pull_number: prNumber,
749
- per_page: 100
750
- });
751
- const comments = response.data.map((c) => ({
752
- id: c.id,
753
- author: c.user?.login ?? "unknown",
754
- body: c.body,
755
- path: c.path,
756
- line: c.line ?? c.original_line ?? null,
757
- side: c.side ?? "RIGHT",
758
- createdAt: c.created_at,
759
- isCopilot: _PullRequestsManager.isCopilotAuthor(c.user?.login ?? "")
760
- }));
761
- this.client.setCache(cacheKey, comments);
762
- return comments;
763
- } catch (error) {
764
- logger.error("Failed to get review comments", {
765
- module: "GitHub",
766
- entityId: prNumber,
767
- error: error instanceof Error ? error.message : String(error)
768
- });
769
- return [];
770
- }
771
- }
772
- async getCopilotReviewSummary(owner, repo, prNumber) {
773
- const [reviews, comments] = await Promise.all([
774
- this.getReviews(owner, repo, prNumber),
775
- this.getReviewComments(owner, repo, prNumber)
776
- ]);
777
- const copilotReviews = reviews.filter((r) => r.isCopilot);
778
- const copilotComments = comments.filter((c) => c.isCopilot);
779
- let state = "none";
780
- if (copilotReviews.length > 0) {
781
- const latest = copilotReviews[copilotReviews.length - 1];
782
- if (latest !== void 0) {
783
- if (latest.state === "APPROVED") state = "approved";
784
- else if (latest.state === "CHANGES_REQUESTED") state = "changes_requested";
785
- else if (latest.state === "COMMENTED") state = "commented";
786
- }
787
- }
788
- return {
789
- prNumber,
790
- state,
791
- commentCount: copilotComments.length,
792
- comments: copilotComments
793
- };
794
- }
795
- };
796
-
797
- // src/github/github-integration/projects.ts
798
- var ProjectsManager = class {
799
- constructor(client) {
800
- this.client = client;
801
- }
802
- client;
803
- async getProjectKanban(owner, projectNumber, repo) {
804
- if (!this.client.graphqlWithAuth) {
805
- logger.debug("GraphQL not available - no token", { module: "GitHub" });
806
- return null;
807
- }
808
- const projectFragment = `
809
- fragment ProjectData on ProjectV2 {
810
- id
811
- title
812
- fields(first: 20) {
813
- nodes {
814
- ... on ProjectV2SingleSelectField {
815
- id
816
- name
817
- options {
818
- id
819
- name
820
- color
821
- }
822
- }
823
- }
824
- }
825
- items(first: 100) {
826
- nodes {
827
- id
828
- type
829
- createdAt
830
- updatedAt
831
- fieldValues(first: 10) {
832
- nodes {
833
- ... on ProjectV2ItemFieldSingleSelectValue {
834
- name
835
- field {
836
- ... on ProjectV2SingleSelectField {
837
- name
838
- }
839
- }
840
- }
841
- }
842
- }
843
- content {
844
- ... on Issue {
845
- number
846
- title
847
- url
848
- labels(first: 5) {
849
- nodes { name }
850
- }
851
- assignees(first: 5) {
852
- nodes { login }
853
- }
854
- }
855
- ... on PullRequest {
856
- number
857
- title
858
- url
859
- labels(first: 5) {
860
- nodes { name }
861
- }
862
- assignees(first: 5) {
863
- nodes { login }
864
- }
865
- }
866
- ... on DraftIssue {
867
- title
868
- }
869
- }
870
- }
871
- }
872
- }
873
- `;
874
- const userQuery = `
875
- ${projectFragment}
876
- query($owner: String!, $number: Int!) {
877
- user(login: $owner) {
878
- projectV2(number: $number) {
879
- ...ProjectData
880
- }
881
- }
882
- }
883
- `;
884
- const repoQuery = `
885
- ${projectFragment}
886
- query($owner: String!, $repo: String!, $number: Int!) {
887
- repository(owner: $owner, name: $repo) {
888
- projectV2(number: $number) {
889
- ...ProjectData
890
- }
891
- }
892
- }
893
- `;
894
- const orgQuery = `
895
- ${projectFragment}
896
- query($owner: String!, $number: Int!) {
897
- organization(login: $owner) {
898
- projectV2(number: $number) {
899
- ...ProjectData
900
- }
901
- }
902
- }
903
- `;
904
- let project = null;
905
- let source = "";
906
- try {
907
- const response = await this.client.graphqlWithAuth(userQuery, {
908
- owner,
909
- number: projectNumber
910
- });
911
- if (response.user?.projectV2) {
912
- project = response.user.projectV2;
913
- source = "user";
914
- }
915
- } catch {
916
- logger.debug("User project not found, trying repository...", { module: "GitHub" });
917
- }
918
- if (!project && repo) {
919
- try {
920
- const response = await this.client.graphqlWithAuth(repoQuery, {
921
- owner,
922
- repo,
923
- number: projectNumber
924
- });
925
- if (response.repository?.projectV2) {
926
- project = response.repository.projectV2;
927
- source = "repository";
928
- }
929
- } catch {
930
- logger.debug("Repository project not found, trying organization...", {
931
- module: "GitHub"
932
- });
933
- }
934
- }
935
- if (!project) {
936
- try {
937
- const response = await this.client.graphqlWithAuth(orgQuery, {
938
- owner,
939
- number: projectNumber
940
- });
941
- if (response.organization?.projectV2) {
942
- project = response.organization.projectV2;
943
- source = "organization";
944
- }
945
- } catch {
946
- logger.debug("Organization project not found", { module: "GitHub" });
947
- }
948
- }
949
- if (!project) {
950
- logger.warning("Project not found", { module: "GitHub", entityId: projectNumber });
951
- return null;
952
- }
953
- const statusField = project.fields.nodes.find(
954
- (f) => f.name === "Status" && f.options !== void 0 && f.options.length > 0
955
- );
956
- if (!statusField?.id || !statusField.options) {
957
- logger.warning("Status field not found in project", {
958
- module: "GitHub",
959
- entityId: projectNumber
960
- });
961
- return null;
962
- }
963
- const statusOptions = statusField.options.map((opt) => ({
964
- id: opt.id,
965
- name: opt.name,
966
- color: opt.color
967
- }));
968
- const columnMap = /* @__PURE__ */ new Map();
969
- for (const opt of statusOptions) {
970
- columnMap.set(opt.name, []);
971
- }
972
- columnMap.set("No Status", []);
973
- for (const item of project.items.nodes) {
974
- const statusValue = item.fieldValues.nodes.find((fv) => fv.field?.name === "Status");
975
- const status = statusValue?.name ?? "No Status";
976
- const content = item.content;
977
- const projectItem = {
978
- id: item.id,
979
- title: content?.title ?? "Draft Issue",
980
- url: content?.url ?? "",
981
- type: item.type,
982
- status,
983
- number: content?.number,
984
- labels: content?.labels?.nodes.map((l) => l.name) ?? [],
985
- assignees: content?.assignees?.nodes.map((a) => a.login) ?? [],
986
- createdAt: item.createdAt,
987
- updatedAt: item.updatedAt
988
- };
989
- const column = columnMap.get(status);
990
- if (column) {
991
- column.push(projectItem);
992
- } else {
993
- columnMap.get("No Status")?.push(projectItem);
994
- }
995
- }
996
- const columns = [];
997
- for (const opt of statusOptions) {
998
- const items = columnMap.get(opt.name) ?? [];
999
- columns.push({
1000
- status: opt.name,
1001
- statusOptionId: opt.id,
1002
- items
1003
- });
1004
- }
1005
- const noStatusItems = columnMap.get("No Status") ?? [];
1006
- if (noStatusItems.length > 0) {
1007
- columns.push({
1008
- status: "No Status",
1009
- statusOptionId: "",
1010
- items: noStatusItems
1011
- });
1012
- }
1013
- const totalItems = project.items.nodes.length;
1014
- logger.info("Fetched Kanban board", {
1015
- module: "GitHub",
1016
- entityId: projectNumber,
1017
- context: { columns: columns.length, items: totalItems, source }
1018
- });
1019
- return {
1020
- projectId: project.id,
1021
- projectNumber,
1022
- projectTitle: project.title,
1023
- statusFieldId: statusField.id,
1024
- statusOptions,
1025
- columns,
1026
- totalItems
1027
- };
1028
- }
1029
- async moveProjectItem(projectId, itemId, statusFieldId, statusOptionId) {
1030
- if (!this.client.graphqlWithAuth) {
1031
- return { success: false, error: "GraphQL not available - no token" };
1032
- }
1033
- try {
1034
- const mutation = `
1035
- mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
1036
- updateProjectV2ItemFieldValue(
1037
- input: {
1038
- projectId: $projectId
1039
- itemId: $itemId
1040
- fieldId: $fieldId
1041
- value: { singleSelectOptionId: $optionId }
1042
- }
1043
- ) {
1044
- projectV2Item {
1045
- id
1046
- }
1047
- }
1048
- }
1049
- `;
1050
- await this.client.graphqlWithAuth(mutation, {
1051
- projectId,
1052
- itemId,
1053
- fieldId: statusFieldId,
1054
- optionId: statusOptionId
1055
- });
1056
- logger.info("Moved project item", {
1057
- module: "GitHub",
1058
- entityId: itemId,
1059
- context: { targetStatus: statusOptionId }
1060
- });
1061
- return { success: true };
1062
- } catch (error) {
1063
- const errorMessage = error instanceof Error ? error.message : String(error);
1064
- logger.error("Failed to move project item", {
1065
- module: "GitHub",
1066
- entityId: itemId,
1067
- error: errorMessage
1068
- });
1069
- return { success: false, error: errorMessage };
1070
- } finally {
1071
- if (typeof this.client.invalidateCache === "function") {
1072
- this.client.invalidateCache("kanban:");
1073
- }
1074
- }
1075
- }
1076
- async addProjectItem(projectId, contentId) {
1077
- if (!this.client.graphqlWithAuth) {
1078
- return { success: false, error: "GraphQL not available - no token" };
1079
- }
1080
- try {
1081
- const mutation = `
1082
- mutation($projectId: ID!, $contentId: ID!) {
1083
- addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
1084
- item {
1085
- id
1086
- }
1087
- }
1088
- }
1089
- `;
1090
- const response = await this.client.graphqlWithAuth(mutation, {
1091
- projectId,
1092
- contentId
1093
- });
1094
- const itemId = response.addProjectV2ItemById?.item?.id;
1095
- logger.info("Added item to project", {
1096
- module: "GitHub",
1097
- context: { projectId, contentId, itemId }
1098
- });
1099
- return { success: true, itemId };
1100
- } catch (error) {
1101
- const errorMessage = error instanceof Error ? error.message : String(error);
1102
- logger.error("Failed to add item to project", {
1103
- module: "GitHub",
1104
- context: { projectId, contentId },
1105
- error: errorMessage
1106
- });
1107
- return { success: false, error: errorMessage };
1108
- } finally {
1109
- if (typeof this.client.invalidateCache === "function") {
1110
- this.client.invalidateCache("kanban:");
1111
- }
1112
- }
1113
- }
1114
- async deleteProjectItem(projectId, itemId) {
1115
- if (!this.client.graphqlWithAuth) {
1116
- return { success: false, error: "GraphQL not available - no token" };
1117
- }
1118
- try {
1119
- const mutation = `
1120
- mutation($projectId: ID!, $itemId: ID!) {
1121
- deleteProjectV2Item(input: { projectId: $projectId, itemId: $itemId }) {
1122
- deletedItemId
1123
- }
1124
- }
1125
- `;
1126
- await this.client.graphqlWithAuth(mutation, {
1127
- projectId,
1128
- itemId
1129
- });
1130
- logger.info("Deleted project item", {
1131
- module: "GitHub",
1132
- context: { projectId, itemId }
1133
- });
1134
- return { success: true };
1135
- } catch (error) {
1136
- const errorMessage = error instanceof Error ? error.message : String(error);
1137
- logger.error("Failed to delete item from project", {
1138
- module: "GitHub",
1139
- context: { projectId, itemId },
1140
- error: errorMessage
1141
- });
1142
- return { success: false, error: errorMessage };
1143
- } finally {
1144
- if (typeof this.client.invalidateCache === "function") {
1145
- this.client.invalidateCache("kanban:");
1146
- }
1147
- }
1148
- }
1149
- };
1150
-
1151
- // src/github/github-integration/milestones.ts
1152
- var MilestonesManager = class {
1153
- constructor(client) {
1154
- this.client = client;
1155
- }
1156
- client;
1157
- async getMilestones(owner, repo, state = "open", limit = 20) {
1158
- if (!this.client.octokit) {
1159
- return [];
1160
- }
1161
- const cacheKey = `milestones:${owner}:${repo}:${state}:${String(limit)}`;
1162
- const cached = this.client.getCached(cacheKey);
1163
- if (cached) return cached;
1164
- try {
1165
- const response = await this.client.octokit.issues.listMilestones({
1166
- owner,
1167
- repo,
1168
- state,
1169
- per_page: limit,
1170
- sort: "due_on",
1171
- direction: "asc"
1172
- });
1173
- const result = response.data.map((ms) => ({
1174
- number: ms.number,
1175
- title: ms.title,
1176
- description: ms.description ?? null,
1177
- state: ms.state === "open" ? "open" : "closed",
1178
- url: ms.html_url,
1179
- dueOn: ms.due_on ?? null,
1180
- openIssues: ms.open_issues,
1181
- closedIssues: ms.closed_issues,
1182
- createdAt: ms.created_at,
1183
- updatedAt: ms.updated_at,
1184
- creator: ms.creator?.login ?? null
1185
- }));
1186
- this.client.setCache(cacheKey, result);
1187
- return result;
1188
- } catch (error) {
1189
- logger.error("Failed to get milestones", {
1190
- module: "GitHub",
1191
- error: error instanceof Error ? error.message : String(error)
1192
- });
1193
- return [];
1194
- }
1195
- }
1196
- async getMilestone(owner, repo, milestoneNumber) {
1197
- if (!this.client.octokit) {
1198
- return null;
1199
- }
1200
- const cacheKey = `milestone:${owner}:${repo}:${String(milestoneNumber)}`;
1201
- const cached = this.client.getCached(cacheKey);
1202
- if (cached !== void 0) return cached;
1203
- try {
1204
- const response = await this.client.octokit.issues.getMilestone({
1205
- owner,
1206
- repo,
1207
- milestone_number: milestoneNumber
1208
- });
1209
- const ms = response.data;
1210
- const milestone = {
1211
- number: ms.number,
1212
- title: ms.title,
1213
- description: ms.description ?? null,
1214
- state: ms.state === "open" ? "open" : "closed",
1215
- url: ms.html_url,
1216
- dueOn: ms.due_on ?? null,
1217
- openIssues: ms.open_issues,
1218
- closedIssues: ms.closed_issues,
1219
- createdAt: ms.created_at,
1220
- updatedAt: ms.updated_at,
1221
- creator: ms.creator?.login ?? null
1222
- };
1223
- this.client.setCache(cacheKey, milestone);
1224
- return milestone;
1225
- } catch (error) {
1226
- logger.error("Failed to get milestone", {
1227
- module: "GitHub",
1228
- entityId: milestoneNumber,
1229
- error: error instanceof Error ? error.message : String(error)
1230
- });
1231
- return null;
1232
- }
1233
- }
1234
- async createMilestone(owner, repo, title, description, dueOn) {
1235
- if (!this.client.octokit) {
1236
- logger.error("Cannot create milestone: GitHub API not available", {
1237
- module: "GitHub"
1238
- });
1239
- return null;
1240
- }
1241
- try {
1242
- const response = await this.client.octokit.issues.createMilestone({
1243
- owner,
1244
- repo,
1245
- title,
1246
- description,
1247
- due_on: dueOn
1248
- });
1249
- const ms = response.data;
1250
- logger.info("Created GitHub milestone", {
1251
- module: "GitHub",
1252
- entityId: ms.number,
1253
- context: { title, owner, repo }
1254
- });
1255
- return {
1256
- number: ms.number,
1257
- title: ms.title,
1258
- description: ms.description ?? null,
1259
- state: ms.state === "open" ? "open" : "closed",
1260
- url: ms.html_url,
1261
- dueOn: ms.due_on ?? null,
1262
- openIssues: ms.open_issues,
1263
- closedIssues: ms.closed_issues,
1264
- createdAt: ms.created_at,
1265
- updatedAt: ms.updated_at,
1266
- creator: ms.creator?.login ?? null
1267
- };
1268
- } catch (error) {
1269
- logger.error("Failed to create milestone", {
1270
- module: "GitHub",
1271
- error: error instanceof Error ? error.message : String(error),
1272
- context: { title, owner, repo }
1273
- });
1274
- return null;
1275
- } finally {
1276
- this.client.invalidateCache(`milestones:${owner}:${repo}`);
1277
- this.client.invalidateCache("context:");
1278
- }
1279
- }
1280
- async updateMilestone(owner, repo, milestoneNumber, updates) {
1281
- if (!this.client.octokit) {
1282
- logger.error("Cannot update milestone: GitHub API not available", {
1283
- module: "GitHub"
1284
- });
1285
- return null;
1286
- }
1287
- try {
1288
- const response = await this.client.octokit.issues.updateMilestone({
1289
- owner,
1290
- repo,
1291
- milestone_number: milestoneNumber,
1292
- title: updates.title,
1293
- description: updates.description,
1294
- due_on: updates.dueOn === null ? void 0 : updates.dueOn,
1295
- state: updates.state
1296
- });
1297
- const ms = response.data;
1298
- logger.info("Updated GitHub milestone", {
1299
- module: "GitHub",
1300
- entityId: milestoneNumber,
1301
- context: { owner, repo, updates: Object.keys(updates) }
1302
- });
1303
- return {
1304
- number: ms.number,
1305
- title: ms.title,
1306
- description: ms.description ?? null,
1307
- state: ms.state === "open" ? "open" : "closed",
1308
- url: ms.html_url,
1309
- dueOn: ms.due_on ?? null,
1310
- openIssues: ms.open_issues,
1311
- closedIssues: ms.closed_issues,
1312
- createdAt: ms.created_at,
1313
- updatedAt: ms.updated_at,
1314
- creator: ms.creator?.login ?? null
1315
- };
1316
- } catch (error) {
1317
- logger.error("Failed to update milestone", {
1318
- module: "GitHub",
1319
- entityId: milestoneNumber,
1320
- error: error instanceof Error ? error.message : String(error)
1321
- });
1322
- return null;
1323
- } finally {
1324
- this.client.invalidateCache(`milestones:${owner}:${repo}`);
1325
- this.client.invalidateCache(`milestone:${owner}:${repo}:${String(milestoneNumber)}`);
1326
- this.client.invalidateCache("context:");
1327
- }
1328
- }
1329
- async deleteMilestone(owner, repo, milestoneNumber) {
1330
- if (!this.client.octokit) {
1331
- return { success: false, error: "GitHub API not available" };
1332
- }
1333
- try {
1334
- await this.client.octokit.issues.deleteMilestone({
1335
- owner,
1336
- repo,
1337
- milestone_number: milestoneNumber
1338
- });
1339
- logger.info("Deleted GitHub milestone", {
1340
- module: "GitHub",
1341
- entityId: milestoneNumber,
1342
- context: { owner, repo }
1343
- });
1344
- return { success: true };
1345
- } catch (error) {
1346
- const errorMessage = error instanceof Error ? error.message : String(error);
1347
- logger.error("Failed to delete milestone", {
1348
- module: "GitHub",
1349
- entityId: milestoneNumber,
1350
- error: errorMessage
1351
- });
1352
- return { success: false, error: errorMessage };
1353
- } finally {
1354
- this.client.invalidateCache(`milestones:${owner}:${repo}`);
1355
- this.client.invalidateCache(`milestone:${owner}:${repo}:${String(milestoneNumber)}`);
1356
- this.client.invalidateCache("context:");
1357
- }
1358
- }
1359
- };
1360
-
1361
- // src/github/github-integration/insights.ts
1362
- var InsightsManager = class {
1363
- constructor(client) {
1364
- this.client = client;
1365
- }
1366
- client;
1367
- async getRepoStats(owner, repo) {
1368
- if (!this.client.octokit) {
1369
- return null;
1370
- }
1371
- const cacheKey = `repostats:${owner}:${repo}`;
1372
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
1373
- if (cached) return cached;
1374
- try {
1375
- const response = await this.client.octokit.repos.get({ owner, repo });
1376
- const data = response.data;
1377
- const result = {
1378
- stars: data.stargazers_count,
1379
- forks: data.forks_count,
1380
- watchers: data.subscribers_count,
1381
- openIssues: data.open_issues_count,
1382
- size: data.size,
1383
- defaultBranch: data.default_branch
1384
- };
1385
- this.client.setCache(cacheKey, result);
1386
- return result;
1387
- } catch (error) {
1388
- logger.error("Failed to get repo stats", {
1389
- module: "GitHub",
1390
- error: error instanceof Error ? error.message : String(error),
1391
- context: { owner, repo }
1392
- });
1393
- return null;
1394
- }
1395
- }
1396
- async getTrafficData(owner, repo) {
1397
- if (!this.client.octokit) {
1398
- return null;
1399
- }
1400
- const cacheKey = `traffic:${owner}:${repo}`;
1401
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
1402
- if (cached) return cached;
1403
- try {
1404
- const [clonesRes, viewsRes] = await Promise.all([
1405
- this.client.octokit.rest.repos.getClones({ owner, repo }),
1406
- this.client.octokit.rest.repos.getViews({ owner, repo })
1407
- ]);
1408
- const clonesDays = clonesRes.data.clones?.length ?? 0;
1409
- const viewsDays = viewsRes.data.views?.length ?? 0;
1410
- const result = {
1411
- clones: {
1412
- total: clonesRes.data.count,
1413
- unique: clonesRes.data.uniques,
1414
- dailyAvg: clonesDays > 0 ? Math.round(clonesRes.data.count / clonesDays) : 0
1415
- },
1416
- views: {
1417
- total: viewsRes.data.count,
1418
- unique: viewsRes.data.uniques,
1419
- dailyAvg: viewsDays > 0 ? Math.round(viewsRes.data.count / viewsDays) : 0
1420
- },
1421
- period: "14 days"
1422
- };
1423
- this.client.setCache(cacheKey, result);
1424
- return result;
1425
- } catch (error) {
1426
- logger.error("Failed to get traffic data", {
1427
- module: "GitHub",
1428
- error: error instanceof Error ? error.message : String(error),
1429
- context: { owner, repo }
1430
- });
1431
- return null;
1432
- }
1433
- }
1434
- async getTopReferrers(owner, repo, limit = 5) {
1435
- if (!this.client.octokit) {
1436
- return [];
1437
- }
1438
- const cacheKey = `referrers:${owner}:${repo}`;
1439
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
1440
- if (cached) return cached.slice(0, limit);
1441
- try {
1442
- const response = await this.client.octokit.rest.repos.getTopReferrers({ owner, repo });
1443
- const result = response.data.map((r) => ({
1444
- referrer: r.referrer,
1445
- count: r.count,
1446
- uniques: r.uniques
1447
- }));
1448
- this.client.setCache(cacheKey, result);
1449
- return result.slice(0, limit);
1450
- } catch (error) {
1451
- logger.error("Failed to get top referrers", {
1452
- module: "GitHub",
1453
- error: error instanceof Error ? error.message : String(error),
1454
- context: { owner, repo }
1455
- });
1456
- return [];
1457
- }
1458
- }
1459
- async getPopularPaths(owner, repo, limit = 5) {
1460
- if (!this.client.octokit) {
1461
- return [];
1462
- }
1463
- const cacheKey = `paths:${owner}:${repo}`;
1464
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
1465
- if (cached) return cached.slice(0, limit);
1466
- try {
1467
- const response = await this.client.octokit.rest.repos.getTopPaths({ owner, repo });
1468
- const result = response.data.map((p) => ({
1469
- path: p.path,
1470
- title: p.title,
1471
- count: p.count,
1472
- uniques: p.uniques
1473
- }));
1474
- this.client.setCache(cacheKey, result);
1475
- return result.slice(0, limit);
1476
- } catch (error) {
1477
- logger.error("Failed to get popular paths", {
1478
- module: "GitHub",
1479
- error: error instanceof Error ? error.message : String(error),
1480
- context: { owner, repo }
1481
- });
1482
- return [];
1483
- }
1484
- }
1485
- };
1486
-
1487
- // src/github/github-integration/repository.ts
1488
- var RepositoryManager = class {
1489
- constructor(client) {
1490
- this.client = client;
1491
- }
1492
- client;
1493
- async getRepoInfo() {
1494
- try {
1495
- const branchResult = await this.client.git.branch();
1496
- const branch = branchResult.current || null;
1497
- const remotes = await this.client.git.getRemotes(true);
1498
- const origin = remotes.find((r) => r.name === "origin");
1499
- const remoteUrl = origin?.refs?.fetch || null;
1500
- const { owner, repo } = this.parseRemoteUrl(remoteUrl);
1501
- const repoInfo = { owner, repo, branch, remoteUrl };
1502
- this.client.cachedRepoInfo = repoInfo;
1503
- return repoInfo;
1504
- } catch (error) {
1505
- logger.debug("Failed to get repo info (may not be a git repo)", {
1506
- module: "GitHub",
1507
- error: error instanceof Error ? error.message : String(error)
1508
- });
1509
- return { owner: null, repo: null, branch: null, remoteUrl: null };
1510
- }
1511
- }
1512
- getCachedRepoInfo() {
1513
- return this.client.cachedRepoInfo;
1514
- }
1515
- setCachedRepoInfo(info) {
1516
- this.client.cachedRepoInfo = info;
1517
- }
1518
- parseRemoteUrl(remoteUrl) {
1519
- if (!remoteUrl) return { owner: null, repo: null };
1520
- if (remoteUrl.startsWith("git@github.com:")) {
1521
- const pathPart = remoteUrl.replace("git@github.com:", "").replace(".git", "");
1522
- const parts = pathPart.split("/");
1523
- if (parts.length >= 2) {
1524
- return { owner: parts[0] ?? null, repo: parts[1] ?? null };
1525
- }
1526
- }
1527
- try {
1528
- const url = new URL(remoteUrl);
1529
- if (url.hostname === "github.com") {
1530
- const path = url.pathname.replace(".git", "").replace(/^\//, "");
1531
- const parts = path.split("/");
1532
- if (parts.length >= 2) {
1533
- return { owner: parts[0] ?? null, repo: parts[1] ?? null };
1534
- }
1535
- }
1536
- } catch {
1537
- }
1538
- return { owner: null, repo: null };
1539
- }
1540
- async getWorkflowRuns(owner, repo, limit = 10) {
1541
- if (!this.client.octokit) {
1542
- logger.debug("GitHub API not available - no token", { module: "GitHub" });
1543
- return [];
1544
- }
1545
- const cacheKey = `workflows:${owner}:${repo}:${String(limit)}`;
1546
- const cached = this.client.getCached(cacheKey);
1547
- if (cached) return cached;
1548
- try {
1549
- const response = await this.client.octokit.rest.actions.listWorkflowRunsForRepo({
1550
- owner,
1551
- repo,
1552
- per_page: limit
1553
- });
1554
- const result = response.data.workflow_runs.map((run) => ({
1555
- id: run.id,
1556
- name: run.name ?? "Unknown Workflow",
1557
- status: run.status,
1558
- conclusion: run.conclusion,
1559
- url: run.html_url,
1560
- headBranch: run.head_branch ?? "",
1561
- headSha: run.head_sha,
1562
- createdAt: run.created_at,
1563
- updatedAt: run.updated_at
1564
- }));
1565
- this.client.setCache(cacheKey, result);
1566
- return result;
1567
- } catch (error) {
1568
- logger.error("Failed to get workflow runs", {
1569
- module: "GitHub",
1570
- error: error instanceof Error ? error.message : String(error)
1571
- });
1572
- return [];
1573
- }
1574
- }
1575
- };
1576
-
1577
- // src/github/github-integration/index.ts
1578
- var GitHubIntegration = class {
1579
- client;
1580
- issuesManager;
1581
- pullRequestsManager;
1582
- projectsManager;
1583
- milestonesManager;
1584
- insightsManager;
1585
- repositoryManager;
1586
- constructor(workingDir = ".") {
1587
- this.client = new GitHubClient(workingDir);
1588
- this.issuesManager = new IssuesManager(this.client);
1589
- this.pullRequestsManager = new PullRequestsManager(this.client);
1590
- this.projectsManager = new ProjectsManager(this.client);
1591
- this.milestonesManager = new MilestonesManager(this.client);
1592
- this.insightsManager = new InsightsManager(this.client);
1593
- this.repositoryManager = new RepositoryManager(this.client);
1594
- }
1595
- isApiAvailable() {
1596
- return this.client.isApiAvailable();
1597
- }
1598
- clearCache() {
1599
- this.client.clearCache();
1600
- }
1601
- async getRepoInfo() {
1602
- return this.repositoryManager.getRepoInfo();
1603
- }
1604
- getCachedRepoInfo() {
1605
- return this.repositoryManager.getCachedRepoInfo();
1606
- }
1607
- setCachedRepoInfo(info) {
1608
- this.repositoryManager.setCachedRepoInfo(info);
1609
- }
1610
- async getIssues(owner, repo, state = "open", limit = 20) {
1611
- return this.issuesManager.getIssues(owner, repo, state, limit);
1612
- }
1613
- async getIssue(owner, repo, issueNumber) {
1614
- return this.issuesManager.getIssue(owner, repo, issueNumber);
1615
- }
1616
- async getIssueComments(owner, repo, issueNumber, limit = 30) {
1617
- return this.issuesManager.getIssueComments(owner, repo, issueNumber, limit);
1618
- }
1619
- async createIssue(owner, repo, title, body, labels, assignees, milestone) {
1620
- return this.issuesManager.createIssue(
1621
- owner,
1622
- repo,
1623
- title,
1624
- body,
1625
- labels,
1626
- assignees,
1627
- milestone
1628
- );
1629
- }
1630
- async closeIssue(owner, repo, issueNumber, comment) {
1631
- return this.issuesManager.closeIssue(owner, repo, issueNumber, comment);
1632
- }
1633
- async getPullRequests(owner, repo, state = "open", limit = 20) {
1634
- return this.pullRequestsManager.getPullRequests(owner, repo, state, limit);
1635
- }
1636
- async getPullRequest(owner, repo, prNumber) {
1637
- return this.pullRequestsManager.getPullRequest(owner, repo, prNumber);
1638
- }
1639
- async getReviews(owner, repo, prNumber) {
1640
- return this.pullRequestsManager.getReviews(owner, repo, prNumber);
1641
- }
1642
- async getReviewComments(owner, repo, prNumber) {
1643
- return this.pullRequestsManager.getReviewComments(owner, repo, prNumber);
1644
- }
1645
- async getCopilotReviewSummary(owner, repo, prNumber) {
1646
- return this.pullRequestsManager.getCopilotReviewSummary(owner, repo, prNumber);
1647
- }
1648
- async getWorkflowRuns(owner, repo, limit = 10) {
1649
- return this.repositoryManager.getWorkflowRuns(owner, repo, limit);
1650
- }
1651
- async getRepoContext() {
1652
- const cached = this.client.getCached("context:repo");
1653
- if (cached) return cached;
1654
- const repoInfo = await this.repositoryManager.getRepoInfo();
1655
- const context = {
1656
- repoName: repoInfo.repo,
1657
- branch: repoInfo.branch,
1658
- commit: null,
1659
- remoteUrl: repoInfo.remoteUrl,
1660
- projects: [],
1661
- issues: [],
1662
- pullRequests: [],
1663
- workflowRuns: [],
1664
- milestones: []
1665
- };
1666
- try {
1667
- const log = await this.client.git.log({ maxCount: 1 });
1668
- context.commit = log.latest?.hash ?? null;
1669
- } catch {
1670
- }
1671
- if (repoInfo.owner && repoInfo.repo) {
1672
- context.issues = await this.issuesManager.getIssues(
1673
- repoInfo.owner,
1674
- repoInfo.repo,
1675
- "open",
1676
- 10
1677
- );
1678
- context.pullRequests = await this.pullRequestsManager.getPullRequests(
1679
- repoInfo.owner,
1680
- repoInfo.repo,
1681
- "open",
1682
- 10
1683
- );
1684
- context.workflowRuns = await this.repositoryManager.getWorkflowRuns(
1685
- repoInfo.owner,
1686
- repoInfo.repo,
1687
- 10
1688
- );
1689
- context.milestones = await this.milestonesManager.getMilestones(
1690
- repoInfo.owner,
1691
- repoInfo.repo,
1692
- "open",
1693
- 10
1694
- );
1695
- }
1696
- this.client.setCache("context:repo", context);
1697
- return context;
1698
- }
1699
- async getProjectKanban(owner, projectNumber, repo) {
1700
- return this.projectsManager.getProjectKanban(owner, projectNumber, repo);
1701
- }
1702
- async moveProjectItem(projectId, itemId, statusFieldId, statusOptionId) {
1703
- return this.projectsManager.moveProjectItem(
1704
- projectId,
1705
- itemId,
1706
- statusFieldId,
1707
- statusOptionId
1708
- );
1709
- }
1710
- async addProjectItem(projectId, contentId) {
1711
- return this.projectsManager.addProjectItem(projectId, contentId);
1712
- }
1713
- async deleteProjectItem(projectId, itemId) {
1714
- return this.projectsManager.deleteProjectItem(projectId, itemId);
1715
- }
1716
- async getMilestones(owner, repo, state = "open", limit = 20) {
1717
- return this.milestonesManager.getMilestones(owner, repo, state, limit);
1718
- }
1719
- async getMilestone(owner, repo, milestoneNumber) {
1720
- return this.milestonesManager.getMilestone(owner, repo, milestoneNumber);
1721
- }
1722
- async createMilestone(owner, repo, title, description, dueOn) {
1723
- return this.milestonesManager.createMilestone(owner, repo, title, description, dueOn);
1724
- }
1725
- async updateMilestone(owner, repo, milestoneNumber, updates) {
1726
- return this.milestonesManager.updateMilestone(owner, repo, milestoneNumber, updates);
1727
- }
1728
- async deleteMilestone(owner, repo, milestoneNumber) {
1729
- return this.milestonesManager.deleteMilestone(owner, repo, milestoneNumber);
1730
- }
1731
- async getRepoStats(owner, repo) {
1732
- return this.insightsManager.getRepoStats(owner, repo);
1733
- }
1734
- async getTrafficData(owner, repo) {
1735
- return this.insightsManager.getTrafficData(owner, repo);
1736
- }
1737
- async getTopReferrers(owner, repo, limit = 5) {
1738
- return this.insightsManager.getTopReferrers(owner, repo, limit);
1739
- }
1740
- async getPopularPaths(owner, repo, limit = 5) {
1741
- return this.insightsManager.getPopularPaths(owner, repo, limit);
1742
- }
1743
- };
1744
-
1745
- export { ConfigurationError, ConnectionError, GitHubIntegration, MemoryJournalMcpError, QueryError, ResourceNotFoundError, ValidationError, assertNoPathTraversal, assertSafeDirectoryPath, logger, matchSuggestion, resolveAuthor, validateDateFormatPattern };