memory-journal-mcp 4.3.0 → 4.4.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.
- package/.dockerignore +131 -122
- package/.gitattributes +29 -0
- package/.github/workflows/docker-publish.yml +1 -1
- package/.github/workflows/lint-and-test.yml +1 -2
- package/.github/workflows/secrets-scanning.yml +0 -1
- package/.github/workflows/security-update.yml +6 -6
- package/.vscode/settings.json +17 -15
- package/CHANGELOG.md +1065 -11
- package/DOCKER_README.md +51 -33
- package/Dockerfile +14 -12
- package/README.md +68 -33
- package/SECURITY.md +225 -220
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +70 -26
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -0
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +6 -0
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +51 -10
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +143 -43
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +7 -1
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts +74 -2
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +508 -7
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/index.js +1 -0
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +257 -13
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +595 -8
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/server/McpServer.d.ts +2 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +69 -26
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/index.d.ts +97 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progress-utils.d.ts +18 -3
- package/dist/utils/progress-utils.d.ts.map +1 -1
- package/dist/utils/progress-utils.js.map +1 -1
- package/dist/utils/security-utils.d.ts +91 -0
- package/dist/utils/security-utils.d.ts.map +1 -0
- package/dist/utils/security-utils.js +184 -0
- package/dist/utils/security-utils.js.map +1 -0
- package/dist/vector/VectorSearchManager.d.ts +2 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +100 -34
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +46 -37
- package/mcp-config-example.json +0 -2
- package/package.json +21 -14
- package/releases/v4.3.1.md +69 -0
- package/releases/v4.4.0.md +120 -0
- package/server.json +3 -3
- package/src/cli.ts +11 -0
- package/src/constants/ServerInstructions.ts +70 -26
- package/src/constants/icons.ts +7 -0
- package/src/database/SqliteAdapter.ts +165 -44
- package/src/filtering/ToolFilter.ts +7 -1
- package/src/github/GitHubIntegration.ts +588 -8
- package/src/handlers/prompts/index.ts +1 -0
- package/src/handlers/resources/index.ts +318 -12
- package/src/handlers/tools/index.ts +686 -13
- package/src/server/McpServer.ts +79 -37
- package/src/types/index.ts +98 -0
- package/src/utils/logger.ts +10 -1
- package/src/utils/progress-utils.ts +17 -6
- package/src/utils/security-utils.ts +205 -0
- package/src/vector/VectorSearchManager.ts +110 -39
- package/tests/constants/icons.test.ts +102 -0
- package/tests/constants/server-instructions.test.ts +549 -0
- package/tests/database/sqlite-adapter.bench.ts +63 -0
- package/tests/database/sqlite-adapter.test.ts +555 -0
- package/tests/filtering/tool-filter.test.ts +266 -0
- package/tests/github/github-integration.test.ts +1024 -0
- package/tests/handlers/github-resource-handlers.test.ts +473 -0
- package/tests/handlers/github-tool-handlers.test.ts +556 -0
- package/tests/handlers/prompt-handlers.test.ts +91 -0
- package/tests/handlers/resource-handlers.test.ts +339 -0
- package/tests/handlers/tool-handlers.test.ts +497 -0
- package/tests/handlers/vector-tool-handlers.test.ts +238 -0
- package/tests/security/sql-injection.test.ts +347 -0
- package/tests/server/mcp-server.bench.ts +55 -0
- package/tests/server/mcp-server.test.ts +675 -0
- package/tests/utils/logger.test.ts +180 -0
- package/tests/utils/mcp-logger.test.ts +212 -0
- package/tests/utils/progress-utils.test.ts +156 -0
- package/tests/utils/security-utils.test.ts +82 -0
- package/tests/vector/vector-search-manager.test.ts +335 -0
- package/tests/vector/vector-search.bench.ts +53 -0
- package/vitest.config.ts +15 -0
- package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
|
@@ -213,10 +213,18 @@ const RelationshipOutputSchema = z.object({
|
|
|
213
213
|
* Schema for get_entry_by_id output (entry with optional relationships).
|
|
214
214
|
* Handles both success (entry found) and error (entry not found) cases.
|
|
215
215
|
*/
|
|
216
|
+
const ImportanceBreakdownSchema = z.object({
|
|
217
|
+
significance: z.number(),
|
|
218
|
+
relationships: z.number(),
|
|
219
|
+
causal: z.number(),
|
|
220
|
+
recency: z.number(),
|
|
221
|
+
})
|
|
222
|
+
|
|
216
223
|
const EntryByIdOutputSchema = z.object({
|
|
217
224
|
entry: EntryOutputSchema.optional(),
|
|
218
225
|
relationships: z.array(RelationshipOutputSchema).optional(),
|
|
219
226
|
importance: z.number().nullable().optional(), // Computed importance score (0.0-1.0)
|
|
227
|
+
importanceBreakdown: ImportanceBreakdownSchema.optional(), // Weighted component contributions
|
|
220
228
|
error: z.string().optional(),
|
|
221
229
|
})
|
|
222
230
|
|
|
@@ -362,6 +370,7 @@ const CrossProjectInsightsOutputSchema = z.object({
|
|
|
362
370
|
last_entry_date: z.string(),
|
|
363
371
|
})
|
|
364
372
|
),
|
|
373
|
+
inactiveThresholdDays: z.number(), // Cutoff for inactive classification
|
|
365
374
|
time_distribution: z.array(
|
|
366
375
|
z.object({
|
|
367
376
|
project_number: z.number(),
|
|
@@ -399,6 +408,7 @@ const DeleteEntryOutputSchema = z.object({
|
|
|
399
408
|
success: z.boolean(),
|
|
400
409
|
entryId: z.number(),
|
|
401
410
|
permanent: z.boolean(),
|
|
411
|
+
error: z.string().optional(),
|
|
402
412
|
})
|
|
403
413
|
|
|
404
414
|
/**
|
|
@@ -423,6 +433,13 @@ const GitHubIssueOutputSchema = z.object({
|
|
|
423
433
|
title: z.string(),
|
|
424
434
|
url: z.string(),
|
|
425
435
|
state: z.enum(['OPEN', 'CLOSED']),
|
|
436
|
+
milestone: z
|
|
437
|
+
.object({
|
|
438
|
+
number: z.number(),
|
|
439
|
+
title: z.string(),
|
|
440
|
+
})
|
|
441
|
+
.nullable()
|
|
442
|
+
.optional(),
|
|
426
443
|
})
|
|
427
444
|
|
|
428
445
|
/**
|
|
@@ -593,6 +610,63 @@ const KanbanBoardOutputSchema = z.object({
|
|
|
593
610
|
instruction: z.string().optional(),
|
|
594
611
|
})
|
|
595
612
|
|
|
613
|
+
// ============================================================================
|
|
614
|
+
// Phase 3b: Repository Insights Output Schema
|
|
615
|
+
// ============================================================================
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Schema for get_repo_insights output.
|
|
619
|
+
* Uses optional sections to minimize token usage.
|
|
620
|
+
*/
|
|
621
|
+
const RepoInsightsOutputSchema = z.object({
|
|
622
|
+
owner: z.string().optional(),
|
|
623
|
+
repo: z.string().optional(),
|
|
624
|
+
section: z.string().optional(),
|
|
625
|
+
stars: z.number().optional(),
|
|
626
|
+
forks: z.number().optional(),
|
|
627
|
+
watchers: z.number().optional(),
|
|
628
|
+
openIssues: z.number().optional(),
|
|
629
|
+
size: z.number().optional(),
|
|
630
|
+
defaultBranch: z.string().optional(),
|
|
631
|
+
traffic: z
|
|
632
|
+
.object({
|
|
633
|
+
clones: z.object({
|
|
634
|
+
total: z.number(),
|
|
635
|
+
unique: z.number(),
|
|
636
|
+
dailyAvg: z.number(),
|
|
637
|
+
}),
|
|
638
|
+
views: z.object({
|
|
639
|
+
total: z.number(),
|
|
640
|
+
unique: z.number(),
|
|
641
|
+
dailyAvg: z.number(),
|
|
642
|
+
}),
|
|
643
|
+
period: z.string(),
|
|
644
|
+
})
|
|
645
|
+
.optional(),
|
|
646
|
+
referrers: z
|
|
647
|
+
.array(
|
|
648
|
+
z.object({
|
|
649
|
+
referrer: z.string(),
|
|
650
|
+
count: z.number(),
|
|
651
|
+
uniques: z.number(),
|
|
652
|
+
})
|
|
653
|
+
)
|
|
654
|
+
.optional(),
|
|
655
|
+
paths: z
|
|
656
|
+
.array(
|
|
657
|
+
z.object({
|
|
658
|
+
path: z.string(),
|
|
659
|
+
title: z.string(),
|
|
660
|
+
count: z.number(),
|
|
661
|
+
uniques: z.number(),
|
|
662
|
+
})
|
|
663
|
+
)
|
|
664
|
+
.optional(),
|
|
665
|
+
error: z.string().optional(),
|
|
666
|
+
requiresUserInput: z.boolean().optional(),
|
|
667
|
+
instruction: z.string().optional(),
|
|
668
|
+
})
|
|
669
|
+
|
|
596
670
|
// ============================================================================
|
|
597
671
|
// Phase 4: Backup Tool Output Schemas
|
|
598
672
|
// ============================================================================
|
|
@@ -770,6 +844,93 @@ const CloseGitHubIssueWithEntryOutputSchema = z.object({
|
|
|
770
844
|
instruction: z.string().optional(),
|
|
771
845
|
})
|
|
772
846
|
|
|
847
|
+
// ============================================================================
|
|
848
|
+
// GitHub Milestone Output Schemas
|
|
849
|
+
// ============================================================================
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* GitHub milestone schema.
|
|
853
|
+
*/
|
|
854
|
+
const GitHubMilestoneOutputSchema = z.object({
|
|
855
|
+
number: z.number(),
|
|
856
|
+
title: z.string(),
|
|
857
|
+
description: z.string().nullable(),
|
|
858
|
+
state: z.enum(['open', 'closed']),
|
|
859
|
+
url: z.string(),
|
|
860
|
+
dueOn: z.string().nullable(),
|
|
861
|
+
openIssues: z.number(),
|
|
862
|
+
closedIssues: z.number(),
|
|
863
|
+
completionPercentage: z.number().optional(),
|
|
864
|
+
createdAt: z.string(),
|
|
865
|
+
updatedAt: z.string(),
|
|
866
|
+
creator: z.string().nullable(),
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Schema for get_github_milestones output.
|
|
871
|
+
*/
|
|
872
|
+
const GitHubMilestonesListOutputSchema = z.object({
|
|
873
|
+
owner: z.string().optional(),
|
|
874
|
+
repo: z.string().optional(),
|
|
875
|
+
detectedOwner: z.string().nullable().optional(),
|
|
876
|
+
detectedRepo: z.string().nullable().optional(),
|
|
877
|
+
milestones: z.array(GitHubMilestoneOutputSchema).optional(),
|
|
878
|
+
count: z.number().optional(),
|
|
879
|
+
error: z.string().optional(),
|
|
880
|
+
requiresUserInput: z.boolean().optional(),
|
|
881
|
+
instruction: z.string().optional(),
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Schema for get_github_milestone output.
|
|
886
|
+
*/
|
|
887
|
+
const GitHubMilestoneResultOutputSchema = z.object({
|
|
888
|
+
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
889
|
+
owner: z.string().optional(),
|
|
890
|
+
repo: z.string().optional(),
|
|
891
|
+
detectedOwner: z.string().nullable().optional(),
|
|
892
|
+
detectedRepo: z.string().nullable().optional(),
|
|
893
|
+
error: z.string().optional(),
|
|
894
|
+
requiresUserInput: z.boolean().optional(),
|
|
895
|
+
instruction: z.string().optional(),
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Schema for create_github_milestone output.
|
|
900
|
+
*/
|
|
901
|
+
const CreateMilestoneOutputSchema = z.object({
|
|
902
|
+
success: z.boolean().optional(),
|
|
903
|
+
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
904
|
+
message: z.string().optional(),
|
|
905
|
+
error: z.string().optional(),
|
|
906
|
+
requiresUserInput: z.boolean().optional(),
|
|
907
|
+
instruction: z.string().optional(),
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Schema for update_github_milestone output.
|
|
912
|
+
*/
|
|
913
|
+
const UpdateMilestoneOutputSchema = z.object({
|
|
914
|
+
success: z.boolean().optional(),
|
|
915
|
+
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
916
|
+
message: z.string().optional(),
|
|
917
|
+
error: z.string().optional(),
|
|
918
|
+
requiresUserInput: z.boolean().optional(),
|
|
919
|
+
instruction: z.string().optional(),
|
|
920
|
+
})
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Schema for delete_github_milestone output.
|
|
924
|
+
*/
|
|
925
|
+
const DeleteMilestoneOutputSchema = z.object({
|
|
926
|
+
success: z.boolean().optional(),
|
|
927
|
+
milestoneNumber: z.number().optional(),
|
|
928
|
+
message: z.string().optional(),
|
|
929
|
+
error: z.string().optional(),
|
|
930
|
+
requiresUserInput: z.boolean().optional(),
|
|
931
|
+
instruction: z.string().optional(),
|
|
932
|
+
})
|
|
933
|
+
|
|
773
934
|
/**
|
|
774
935
|
* Schema for cleanup_backups output.
|
|
775
936
|
*/
|
|
@@ -915,8 +1076,13 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
915
1076
|
if (!entry) {
|
|
916
1077
|
return Promise.resolve({ error: `Entry ${entry_id} not found` })
|
|
917
1078
|
}
|
|
918
|
-
const importance =
|
|
919
|
-
|
|
1079
|
+
const { score: importance, breakdown: importanceBreakdown } =
|
|
1080
|
+
db.calculateImportance(entry_id)
|
|
1081
|
+
const result: Record<string, unknown> = {
|
|
1082
|
+
entry,
|
|
1083
|
+
importance,
|
|
1084
|
+
importanceBreakdown,
|
|
1085
|
+
}
|
|
920
1086
|
if (include_relationships) {
|
|
921
1087
|
result['relationships'] = db.getRelationships(entry_id)
|
|
922
1088
|
}
|
|
@@ -1162,8 +1328,13 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
1162
1328
|
|
|
1163
1329
|
if (!projectsResult[0] || projectsResult[0].values.length === 0) {
|
|
1164
1330
|
return Promise.resolve({
|
|
1165
|
-
|
|
1331
|
+
project_count: 0,
|
|
1332
|
+
total_entries: 0,
|
|
1166
1333
|
projects: [],
|
|
1334
|
+
inactive_projects: [],
|
|
1335
|
+
inactiveThresholdDays: 7,
|
|
1336
|
+
time_distribution: [],
|
|
1337
|
+
message: `No projects found with at least ${String(input.min_entries)} entries`,
|
|
1167
1338
|
})
|
|
1168
1339
|
}
|
|
1169
1340
|
|
|
@@ -1238,6 +1409,7 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
1238
1409
|
top_tags: projectTags[p['project_number'] as number] ?? [],
|
|
1239
1410
|
})),
|
|
1240
1411
|
inactive_projects: inactiveProjects,
|
|
1412
|
+
inactiveThresholdDays: 7,
|
|
1241
1413
|
time_distribution: distribution,
|
|
1242
1414
|
})
|
|
1243
1415
|
},
|
|
@@ -1271,13 +1443,29 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
1271
1443
|
})
|
|
1272
1444
|
}
|
|
1273
1445
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1446
|
+
// P154: linkEntries throws for nonexistent entries
|
|
1447
|
+
try {
|
|
1448
|
+
const relationship = db.linkEntries(
|
|
1449
|
+
input.from_entry_id,
|
|
1450
|
+
input.to_entry_id,
|
|
1451
|
+
input.relationship_type as RelationshipType,
|
|
1452
|
+
input.description
|
|
1453
|
+
)
|
|
1454
|
+
return Promise.resolve({ success: true, relationship })
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
return Promise.resolve({
|
|
1457
|
+
success: false,
|
|
1458
|
+
relationship: {
|
|
1459
|
+
id: 0,
|
|
1460
|
+
fromEntryId: input.from_entry_id,
|
|
1461
|
+
toEntryId: input.to_entry_id,
|
|
1462
|
+
relationshipType: input.relationship_type,
|
|
1463
|
+
description: input.description ?? null,
|
|
1464
|
+
createdAt: '',
|
|
1465
|
+
},
|
|
1466
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
1467
|
+
})
|
|
1468
|
+
}
|
|
1281
1469
|
},
|
|
1282
1470
|
},
|
|
1283
1471
|
{
|
|
@@ -1316,6 +1504,19 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
1316
1504
|
let entriesResult
|
|
1317
1505
|
|
|
1318
1506
|
if (input.entry_id !== undefined) {
|
|
1507
|
+
// P154: Pre-check entry existence to disambiguate responses
|
|
1508
|
+
const entry = db.getEntryById(input.entry_id)
|
|
1509
|
+
if (!entry) {
|
|
1510
|
+
return Promise.resolve({
|
|
1511
|
+
entry_count: 0,
|
|
1512
|
+
relationship_count: 0,
|
|
1513
|
+
root_entry: input.entry_id,
|
|
1514
|
+
depth: input.depth,
|
|
1515
|
+
mermaid: null,
|
|
1516
|
+
message: `Entry ${String(input.entry_id)} not found`,
|
|
1517
|
+
})
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1319
1520
|
// Use recursive CTE to get connected entries up to depth
|
|
1320
1521
|
entriesResult = rawDb.exec(
|
|
1321
1522
|
`
|
|
@@ -1378,8 +1579,12 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
1378
1579
|
|
|
1379
1580
|
if (!entriesResult[0] || entriesResult[0].values.length === 0) {
|
|
1380
1581
|
return Promise.resolve({
|
|
1381
|
-
|
|
1582
|
+
entry_count: 0,
|
|
1583
|
+
relationship_count: 0,
|
|
1584
|
+
root_entry: input.entry_id ?? null,
|
|
1585
|
+
depth: input.depth,
|
|
1382
1586
|
mermaid: null,
|
|
1587
|
+
message: 'No entries found with relationships matching your criteria',
|
|
1383
1588
|
})
|
|
1384
1589
|
}
|
|
1385
1590
|
|
|
@@ -1569,8 +1774,18 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
1569
1774
|
const { entry_id, permanent } = DeleteEntrySchema.parse(params)
|
|
1570
1775
|
const success = db.deleteEntry(entry_id, permanent)
|
|
1571
1776
|
|
|
1777
|
+
// P154: Surface structured error for nonexistent entries
|
|
1778
|
+
if (!success) {
|
|
1779
|
+
return Promise.resolve({
|
|
1780
|
+
success: false,
|
|
1781
|
+
entryId: entry_id,
|
|
1782
|
+
permanent,
|
|
1783
|
+
error: `Entry ${String(entry_id)} not found`,
|
|
1784
|
+
})
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1572
1787
|
// Remove from vector index (non-critical if fails)
|
|
1573
|
-
if (
|
|
1788
|
+
if (vectorManager) {
|
|
1574
1789
|
vectorManager.removeEntry(entry_id).catch(() => {
|
|
1575
1790
|
// Non-critical failure, entry already deleted from DB
|
|
1576
1791
|
})
|
|
@@ -2146,6 +2361,10 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
2146
2361
|
body: z.string().optional().describe('Issue body/description'),
|
|
2147
2362
|
labels: z.array(z.string()).optional().describe('Labels to apply'),
|
|
2148
2363
|
assignees: z.array(z.string()).optional().describe('Users to assign'),
|
|
2364
|
+
milestone_number: z
|
|
2365
|
+
.number()
|
|
2366
|
+
.optional()
|
|
2367
|
+
.describe('Milestone number to assign this issue to'),
|
|
2149
2368
|
project_number: z
|
|
2150
2369
|
.number()
|
|
2151
2370
|
.optional()
|
|
@@ -2179,6 +2398,7 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
2179
2398
|
body: z.string().optional(),
|
|
2180
2399
|
labels: z.array(z.string()).optional(),
|
|
2181
2400
|
assignees: z.array(z.string()).optional(),
|
|
2401
|
+
milestone_number: z.number().optional(),
|
|
2182
2402
|
project_number: z.number().optional(),
|
|
2183
2403
|
initial_status: z.string().optional(),
|
|
2184
2404
|
owner: z.string().optional(),
|
|
@@ -2212,7 +2432,8 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
2212
2432
|
input.title,
|
|
2213
2433
|
input.body,
|
|
2214
2434
|
input.labels,
|
|
2215
|
-
input.assignees
|
|
2435
|
+
input.assignees,
|
|
2436
|
+
input.milestone_number
|
|
2216
2437
|
)
|
|
2217
2438
|
|
|
2218
2439
|
if (!issue) {
|
|
@@ -2540,6 +2761,458 @@ function getAllToolDefinitions(context: ToolContext): ToolDefinition[] {
|
|
|
2540
2761
|
}
|
|
2541
2762
|
},
|
|
2542
2763
|
},
|
|
2764
|
+
// Milestone tools
|
|
2765
|
+
{
|
|
2766
|
+
name: 'get_github_milestones',
|
|
2767
|
+
title: 'List GitHub Milestones',
|
|
2768
|
+
description:
|
|
2769
|
+
'List GitHub milestones for the repository with completion percentages and due dates.',
|
|
2770
|
+
group: 'github',
|
|
2771
|
+
inputSchema: z.object({
|
|
2772
|
+
state: z
|
|
2773
|
+
.enum(['open', 'closed', 'all'])
|
|
2774
|
+
.optional()
|
|
2775
|
+
.default('open')
|
|
2776
|
+
.describe('Filter by state (default: open)'),
|
|
2777
|
+
limit: z
|
|
2778
|
+
.number()
|
|
2779
|
+
.optional()
|
|
2780
|
+
.default(20)
|
|
2781
|
+
.describe('Max milestones to return (default: 20)'),
|
|
2782
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2783
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2784
|
+
}),
|
|
2785
|
+
outputSchema: GitHubMilestonesListOutputSchema,
|
|
2786
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2787
|
+
handler: async (params: unknown) => {
|
|
2788
|
+
const input = z
|
|
2789
|
+
.object({
|
|
2790
|
+
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
2791
|
+
limit: z.number().optional().default(20),
|
|
2792
|
+
owner: z.string().optional(),
|
|
2793
|
+
repo: z.string().optional(),
|
|
2794
|
+
})
|
|
2795
|
+
.parse(params)
|
|
2796
|
+
|
|
2797
|
+
if (!github) {
|
|
2798
|
+
return { error: 'GitHub integration not available' }
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
const repoInfo = await github.getRepoInfo()
|
|
2802
|
+
const detectedOwner = repoInfo.owner
|
|
2803
|
+
const detectedRepo = repoInfo.repo
|
|
2804
|
+
|
|
2805
|
+
const owner = input.owner ?? detectedOwner ?? undefined
|
|
2806
|
+
const repo = input.repo ?? detectedRepo ?? undefined
|
|
2807
|
+
|
|
2808
|
+
if (!owner || !repo) {
|
|
2809
|
+
return {
|
|
2810
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2811
|
+
requiresUserInput: true,
|
|
2812
|
+
detectedOwner,
|
|
2813
|
+
detectedRepo,
|
|
2814
|
+
instruction:
|
|
2815
|
+
'Ask the user: "What GitHub repository should I list milestones for? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
const milestones = await github.getMilestones(owner, repo, input.state, input.limit)
|
|
2820
|
+
const milestonesWithPercentage = milestones.map((ms) => {
|
|
2821
|
+
const total = ms.openIssues + ms.closedIssues
|
|
2822
|
+
const completionPercentage =
|
|
2823
|
+
total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0
|
|
2824
|
+
return { ...ms, completionPercentage }
|
|
2825
|
+
})
|
|
2826
|
+
|
|
2827
|
+
return {
|
|
2828
|
+
milestones: milestonesWithPercentage,
|
|
2829
|
+
count: milestonesWithPercentage.length,
|
|
2830
|
+
owner,
|
|
2831
|
+
repo,
|
|
2832
|
+
detectedOwner,
|
|
2833
|
+
detectedRepo,
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
},
|
|
2837
|
+
{
|
|
2838
|
+
name: 'get_github_milestone',
|
|
2839
|
+
title: 'Get GitHub Milestone Details',
|
|
2840
|
+
description:
|
|
2841
|
+
'Get detailed information about a specific GitHub milestone including progress and linked issue counts.',
|
|
2842
|
+
group: 'github',
|
|
2843
|
+
inputSchema: z.object({
|
|
2844
|
+
milestone_number: z.number().describe('Milestone number'),
|
|
2845
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2846
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2847
|
+
}),
|
|
2848
|
+
outputSchema: GitHubMilestoneResultOutputSchema,
|
|
2849
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2850
|
+
handler: async (params: unknown) => {
|
|
2851
|
+
const input = z
|
|
2852
|
+
.object({
|
|
2853
|
+
milestone_number: z.number(),
|
|
2854
|
+
owner: z.string().optional(),
|
|
2855
|
+
repo: z.string().optional(),
|
|
2856
|
+
})
|
|
2857
|
+
.parse(params)
|
|
2858
|
+
|
|
2859
|
+
if (!github) {
|
|
2860
|
+
return { error: 'GitHub integration not available' }
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
const repoInfo = await github.getRepoInfo()
|
|
2864
|
+
const detectedOwner = repoInfo.owner
|
|
2865
|
+
const detectedRepo = repoInfo.repo
|
|
2866
|
+
|
|
2867
|
+
const owner = input.owner ?? detectedOwner ?? undefined
|
|
2868
|
+
const repo = input.repo ?? detectedRepo ?? undefined
|
|
2869
|
+
|
|
2870
|
+
if (!owner || !repo) {
|
|
2871
|
+
return {
|
|
2872
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2873
|
+
requiresUserInput: true,
|
|
2874
|
+
detectedOwner,
|
|
2875
|
+
detectedRepo,
|
|
2876
|
+
instruction:
|
|
2877
|
+
'Ask the user: "What GitHub repository is this milestone from? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
const milestone = await github.getMilestone(owner, repo, input.milestone_number)
|
|
2882
|
+
if (!milestone) {
|
|
2883
|
+
return {
|
|
2884
|
+
error: `Milestone #${String(input.milestone_number)} not found`,
|
|
2885
|
+
owner,
|
|
2886
|
+
repo,
|
|
2887
|
+
detectedOwner,
|
|
2888
|
+
detectedRepo,
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
const total = milestone.openIssues + milestone.closedIssues
|
|
2893
|
+
const completionPercentage =
|
|
2894
|
+
total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0
|
|
2895
|
+
|
|
2896
|
+
return {
|
|
2897
|
+
milestone: { ...milestone, completionPercentage },
|
|
2898
|
+
owner,
|
|
2899
|
+
repo,
|
|
2900
|
+
detectedOwner,
|
|
2901
|
+
detectedRepo,
|
|
2902
|
+
}
|
|
2903
|
+
},
|
|
2904
|
+
},
|
|
2905
|
+
{
|
|
2906
|
+
name: 'create_github_milestone',
|
|
2907
|
+
title: 'Create GitHub Milestone',
|
|
2908
|
+
description:
|
|
2909
|
+
'Create a new GitHub milestone for tracking progress toward a project goal.',
|
|
2910
|
+
group: 'github',
|
|
2911
|
+
inputSchema: z.object({
|
|
2912
|
+
title: z.string().min(1).describe('Milestone title'),
|
|
2913
|
+
description: z.string().optional().describe('Milestone description'),
|
|
2914
|
+
due_on: z.string().optional().describe('Due date in YYYY-MM-DD format (optional)'),
|
|
2915
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2916
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2917
|
+
}),
|
|
2918
|
+
outputSchema: CreateMilestoneOutputSchema,
|
|
2919
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2920
|
+
handler: async (params: unknown) => {
|
|
2921
|
+
const input = z
|
|
2922
|
+
.object({
|
|
2923
|
+
title: z.string().min(1),
|
|
2924
|
+
description: z.string().optional(),
|
|
2925
|
+
due_on: z.string().optional(),
|
|
2926
|
+
owner: z.string().optional(),
|
|
2927
|
+
repo: z.string().optional(),
|
|
2928
|
+
})
|
|
2929
|
+
.parse(params)
|
|
2930
|
+
|
|
2931
|
+
if (!github) {
|
|
2932
|
+
return { error: 'GitHub integration not available' }
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
const repoInfo = await github.getRepoInfo()
|
|
2936
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined
|
|
2937
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined
|
|
2938
|
+
|
|
2939
|
+
if (!owner || !repo) {
|
|
2940
|
+
return {
|
|
2941
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2942
|
+
requiresUserInput: true,
|
|
2943
|
+
instruction:
|
|
2944
|
+
'Ask the user: "What GitHub repository should I create the milestone in?"',
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// Format due_on to ISO 8601 if provided (GitHub expects YYYY-MM-DDTHH:MM:SSZ)
|
|
2949
|
+
const dueOn = input.due_on ? `${input.due_on}T08:00:00Z` : undefined
|
|
2950
|
+
|
|
2951
|
+
const milestone = await github.createMilestone(
|
|
2952
|
+
owner,
|
|
2953
|
+
repo,
|
|
2954
|
+
input.title,
|
|
2955
|
+
input.description,
|
|
2956
|
+
dueOn
|
|
2957
|
+
)
|
|
2958
|
+
|
|
2959
|
+
if (!milestone) {
|
|
2960
|
+
return {
|
|
2961
|
+
error: 'Failed to create milestone. Check GITHUB_TOKEN permissions.',
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
return {
|
|
2966
|
+
success: true,
|
|
2967
|
+
milestone: { ...milestone, completionPercentage: 0 },
|
|
2968
|
+
message: `Created milestone #${String(milestone.number)}: ${milestone.title}`,
|
|
2969
|
+
}
|
|
2970
|
+
},
|
|
2971
|
+
},
|
|
2972
|
+
{
|
|
2973
|
+
name: 'update_github_milestone',
|
|
2974
|
+
title: 'Update GitHub Milestone',
|
|
2975
|
+
description:
|
|
2976
|
+
'Update a GitHub milestone (title, description, due date, or state). Use state "closed" to close a completed milestone.',
|
|
2977
|
+
group: 'github',
|
|
2978
|
+
inputSchema: z.object({
|
|
2979
|
+
milestone_number: z.number().describe('Milestone number to update'),
|
|
2980
|
+
title: z.string().optional().describe('New title'),
|
|
2981
|
+
description: z.string().optional().describe('New description'),
|
|
2982
|
+
due_on: z.string().optional().describe('New due date in YYYY-MM-DD format'),
|
|
2983
|
+
state: z
|
|
2984
|
+
.enum(['open', 'closed'])
|
|
2985
|
+
.optional()
|
|
2986
|
+
.describe('Set to "closed" to close the milestone'),
|
|
2987
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2988
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2989
|
+
}),
|
|
2990
|
+
outputSchema: UpdateMilestoneOutputSchema,
|
|
2991
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2992
|
+
handler: async (params: unknown) => {
|
|
2993
|
+
const input = z
|
|
2994
|
+
.object({
|
|
2995
|
+
milestone_number: z.number(),
|
|
2996
|
+
title: z.string().optional(),
|
|
2997
|
+
description: z.string().optional(),
|
|
2998
|
+
due_on: z.string().optional(),
|
|
2999
|
+
state: z.enum(['open', 'closed']).optional(),
|
|
3000
|
+
owner: z.string().optional(),
|
|
3001
|
+
repo: z.string().optional(),
|
|
3002
|
+
})
|
|
3003
|
+
.parse(params)
|
|
3004
|
+
|
|
3005
|
+
if (!github) {
|
|
3006
|
+
return { error: 'GitHub integration not available' }
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
const repoInfo = await github.getRepoInfo()
|
|
3010
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined
|
|
3011
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined
|
|
3012
|
+
|
|
3013
|
+
if (!owner || !repo) {
|
|
3014
|
+
return {
|
|
3015
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
3016
|
+
requiresUserInput: true,
|
|
3017
|
+
instruction: 'Ask the user: "What GitHub repository is this milestone in?"',
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
const dueOn = input.due_on ? `${input.due_on}T08:00:00Z` : undefined
|
|
3022
|
+
|
|
3023
|
+
const milestone = await github.updateMilestone(
|
|
3024
|
+
owner,
|
|
3025
|
+
repo,
|
|
3026
|
+
input.milestone_number,
|
|
3027
|
+
{
|
|
3028
|
+
title: input.title,
|
|
3029
|
+
description: input.description,
|
|
3030
|
+
dueOn,
|
|
3031
|
+
state: input.state,
|
|
3032
|
+
}
|
|
3033
|
+
)
|
|
3034
|
+
|
|
3035
|
+
if (!milestone) {
|
|
3036
|
+
return {
|
|
3037
|
+
error: `Failed to update milestone #${String(input.milestone_number)}. Check that it exists and GITHUB_TOKEN has permissions.`,
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
const total = milestone.openIssues + milestone.closedIssues
|
|
3042
|
+
const completionPercentage =
|
|
3043
|
+
total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0
|
|
3044
|
+
|
|
3045
|
+
return {
|
|
3046
|
+
success: true,
|
|
3047
|
+
milestone: { ...milestone, completionPercentage },
|
|
3048
|
+
message: `Updated milestone #${String(milestone.number)}: ${milestone.title}`,
|
|
3049
|
+
}
|
|
3050
|
+
},
|
|
3051
|
+
},
|
|
3052
|
+
{
|
|
3053
|
+
name: 'delete_github_milestone',
|
|
3054
|
+
title: 'Delete GitHub Milestone',
|
|
3055
|
+
description:
|
|
3056
|
+
'Permanently delete a GitHub milestone. Issues assigned to the milestone will be un-assigned but not deleted.',
|
|
3057
|
+
group: 'github',
|
|
3058
|
+
inputSchema: z.object({
|
|
3059
|
+
milestone_number: z.number().describe('Milestone number to delete'),
|
|
3060
|
+
confirm: z.literal(true).describe('Must be set to true to confirm deletion'),
|
|
3061
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
3062
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
3063
|
+
}),
|
|
3064
|
+
outputSchema: DeleteMilestoneOutputSchema,
|
|
3065
|
+
annotations: {
|
|
3066
|
+
readOnlyHint: false,
|
|
3067
|
+
idempotentHint: false,
|
|
3068
|
+
destructiveHint: true,
|
|
3069
|
+
openWorldHint: true,
|
|
3070
|
+
},
|
|
3071
|
+
handler: async (params: unknown) => {
|
|
3072
|
+
const input = z
|
|
3073
|
+
.object({
|
|
3074
|
+
milestone_number: z.number(),
|
|
3075
|
+
confirm: z.literal(true),
|
|
3076
|
+
owner: z.string().optional(),
|
|
3077
|
+
repo: z.string().optional(),
|
|
3078
|
+
})
|
|
3079
|
+
.parse(params)
|
|
3080
|
+
|
|
3081
|
+
if (!github) {
|
|
3082
|
+
return { error: 'GitHub integration not available' }
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
const repoInfo = await github.getRepoInfo()
|
|
3086
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined
|
|
3087
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined
|
|
3088
|
+
|
|
3089
|
+
if (!owner || !repo) {
|
|
3090
|
+
return {
|
|
3091
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
3092
|
+
requiresUserInput: true,
|
|
3093
|
+
instruction: 'Ask the user: "What GitHub repository is this milestone in?"',
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
const result = await github.deleteMilestone(owner, repo, input.milestone_number)
|
|
3098
|
+
|
|
3099
|
+
if (!result.success) {
|
|
3100
|
+
return {
|
|
3101
|
+
success: false,
|
|
3102
|
+
milestoneNumber: input.milestone_number,
|
|
3103
|
+
message: `Failed to delete milestone #${String(input.milestone_number)}`,
|
|
3104
|
+
error: result.error ?? undefined,
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
return {
|
|
3109
|
+
success: true,
|
|
3110
|
+
milestoneNumber: input.milestone_number,
|
|
3111
|
+
message: `Deleted milestone #${String(input.milestone_number)}`,
|
|
3112
|
+
}
|
|
3113
|
+
},
|
|
3114
|
+
},
|
|
3115
|
+
// Repository insights tool
|
|
3116
|
+
{
|
|
3117
|
+
name: 'get_repo_insights',
|
|
3118
|
+
title: 'Repository Insights',
|
|
3119
|
+
description:
|
|
3120
|
+
'Get repository insights: stars, forks, traffic (clones/views), referrers, and popular paths. Use "sections" to control token usage: stars (~50 tokens), traffic (~100), referrers (~100), paths (~100), or all (~350).',
|
|
3121
|
+
group: 'github',
|
|
3122
|
+
inputSchema: z.object({
|
|
3123
|
+
sections: z
|
|
3124
|
+
.enum(['stars', 'traffic', 'referrers', 'paths', 'all'])
|
|
3125
|
+
.optional()
|
|
3126
|
+
.default('stars')
|
|
3127
|
+
.describe(
|
|
3128
|
+
'Data section to return (default: stars). Use "all" for full payload.'
|
|
3129
|
+
),
|
|
3130
|
+
owner: z
|
|
3131
|
+
.string()
|
|
3132
|
+
.optional()
|
|
3133
|
+
.describe('Repository owner - LEAVE EMPTY to auto-detect'),
|
|
3134
|
+
repo: z
|
|
3135
|
+
.string()
|
|
3136
|
+
.optional()
|
|
3137
|
+
.describe('Repository name - LEAVE EMPTY to auto-detect'),
|
|
3138
|
+
}),
|
|
3139
|
+
outputSchema: RepoInsightsOutputSchema,
|
|
3140
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
3141
|
+
handler: async (params: unknown) => {
|
|
3142
|
+
const input = z
|
|
3143
|
+
.object({
|
|
3144
|
+
sections: z
|
|
3145
|
+
.enum(['stars', 'traffic', 'referrers', 'paths', 'all'])
|
|
3146
|
+
.optional()
|
|
3147
|
+
.default('stars'),
|
|
3148
|
+
owner: z.string().optional(),
|
|
3149
|
+
repo: z.string().optional(),
|
|
3150
|
+
})
|
|
3151
|
+
.parse(params)
|
|
3152
|
+
|
|
3153
|
+
if (!github) {
|
|
3154
|
+
return { error: 'GitHub integration not available' }
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
const repoInfo = await github.getRepoInfo()
|
|
3158
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined
|
|
3159
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined
|
|
3160
|
+
|
|
3161
|
+
if (!owner || !repo) {
|
|
3162
|
+
return {
|
|
3163
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
3164
|
+
requiresUserInput: true,
|
|
3165
|
+
instruction:
|
|
3166
|
+
'Ask the user: "What GitHub repository should I get insights for? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
const section = input.sections
|
|
3171
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- building response dynamically
|
|
3172
|
+
const result: Record<string, any> = {
|
|
3173
|
+
owner,
|
|
3174
|
+
repo,
|
|
3175
|
+
section,
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
// Stars section (default)
|
|
3179
|
+
if (section === 'stars' || section === 'all') {
|
|
3180
|
+
const stats = await github.getRepoStats(owner, repo)
|
|
3181
|
+
if (stats) {
|
|
3182
|
+
result['stars'] = stats.stars
|
|
3183
|
+
result['forks'] = stats.forks
|
|
3184
|
+
result['watchers'] = stats.watchers
|
|
3185
|
+
result['openIssues'] = stats.openIssues
|
|
3186
|
+
if (section === 'all') {
|
|
3187
|
+
result['size'] = stats.size
|
|
3188
|
+
result['defaultBranch'] = stats.defaultBranch
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
// Traffic section
|
|
3194
|
+
if (section === 'traffic' || section === 'all') {
|
|
3195
|
+
const traffic = await github.getTrafficData(owner, repo)
|
|
3196
|
+
if (traffic) {
|
|
3197
|
+
result['traffic'] = traffic
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
// Referrers section
|
|
3202
|
+
if (section === 'referrers' || section === 'all') {
|
|
3203
|
+
const referrers = await github.getTopReferrers(owner, repo, 5)
|
|
3204
|
+
result['referrers'] = referrers
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
// Paths section
|
|
3208
|
+
if (section === 'paths' || section === 'all') {
|
|
3209
|
+
const paths = await github.getPopularPaths(owner, repo, 5)
|
|
3210
|
+
result['paths'] = paths
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
return result
|
|
3214
|
+
},
|
|
3215
|
+
},
|
|
2543
3216
|
// Backup tools
|
|
2544
3217
|
{
|
|
2545
3218
|
name: 'backup_journal',
|