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
|
@@ -168,10 +168,17 @@ const RelationshipOutputSchema = z.object({
|
|
|
168
168
|
* Schema for get_entry_by_id output (entry with optional relationships).
|
|
169
169
|
* Handles both success (entry found) and error (entry not found) cases.
|
|
170
170
|
*/
|
|
171
|
+
const ImportanceBreakdownSchema = z.object({
|
|
172
|
+
significance: z.number(),
|
|
173
|
+
relationships: z.number(),
|
|
174
|
+
causal: z.number(),
|
|
175
|
+
recency: z.number(),
|
|
176
|
+
});
|
|
171
177
|
const EntryByIdOutputSchema = z.object({
|
|
172
178
|
entry: EntryOutputSchema.optional(),
|
|
173
179
|
relationships: z.array(RelationshipOutputSchema).optional(),
|
|
174
180
|
importance: z.number().nullable().optional(), // Computed importance score (0.0-1.0)
|
|
181
|
+
importanceBreakdown: ImportanceBreakdownSchema.optional(), // Weighted component contributions
|
|
175
182
|
error: z.string().optional(),
|
|
176
183
|
});
|
|
177
184
|
/**
|
|
@@ -300,6 +307,7 @@ const CrossProjectInsightsOutputSchema = z.object({
|
|
|
300
307
|
project_number: z.number(),
|
|
301
308
|
last_entry_date: z.string(),
|
|
302
309
|
})),
|
|
310
|
+
inactiveThresholdDays: z.number(), // Cutoff for inactive classification
|
|
303
311
|
time_distribution: z.array(z.object({
|
|
304
312
|
project_number: z.number(),
|
|
305
313
|
percentage: z.string(),
|
|
@@ -331,6 +339,7 @@ const DeleteEntryOutputSchema = z.object({
|
|
|
331
339
|
success: z.boolean(),
|
|
332
340
|
entryId: z.number(),
|
|
333
341
|
permanent: z.boolean(),
|
|
342
|
+
error: z.string().optional(),
|
|
334
343
|
});
|
|
335
344
|
/**
|
|
336
345
|
* Schema for link_entries output.
|
|
@@ -352,6 +361,13 @@ const GitHubIssueOutputSchema = z.object({
|
|
|
352
361
|
title: z.string(),
|
|
353
362
|
url: z.string(),
|
|
354
363
|
state: z.enum(['OPEN', 'CLOSED']),
|
|
364
|
+
milestone: z
|
|
365
|
+
.object({
|
|
366
|
+
number: z.number(),
|
|
367
|
+
title: z.string(),
|
|
368
|
+
})
|
|
369
|
+
.nullable()
|
|
370
|
+
.optional(),
|
|
355
371
|
});
|
|
356
372
|
/**
|
|
357
373
|
* GitHub issue details schema (extended).
|
|
@@ -510,6 +526,57 @@ const KanbanBoardOutputSchema = z.object({
|
|
|
510
526
|
instruction: z.string().optional(),
|
|
511
527
|
});
|
|
512
528
|
// ============================================================================
|
|
529
|
+
// Phase 3b: Repository Insights Output Schema
|
|
530
|
+
// ============================================================================
|
|
531
|
+
/**
|
|
532
|
+
* Schema for get_repo_insights output.
|
|
533
|
+
* Uses optional sections to minimize token usage.
|
|
534
|
+
*/
|
|
535
|
+
const RepoInsightsOutputSchema = z.object({
|
|
536
|
+
owner: z.string().optional(),
|
|
537
|
+
repo: z.string().optional(),
|
|
538
|
+
section: z.string().optional(),
|
|
539
|
+
stars: z.number().optional(),
|
|
540
|
+
forks: z.number().optional(),
|
|
541
|
+
watchers: z.number().optional(),
|
|
542
|
+
openIssues: z.number().optional(),
|
|
543
|
+
size: z.number().optional(),
|
|
544
|
+
defaultBranch: z.string().optional(),
|
|
545
|
+
traffic: z
|
|
546
|
+
.object({
|
|
547
|
+
clones: z.object({
|
|
548
|
+
total: z.number(),
|
|
549
|
+
unique: z.number(),
|
|
550
|
+
dailyAvg: z.number(),
|
|
551
|
+
}),
|
|
552
|
+
views: z.object({
|
|
553
|
+
total: z.number(),
|
|
554
|
+
unique: z.number(),
|
|
555
|
+
dailyAvg: z.number(),
|
|
556
|
+
}),
|
|
557
|
+
period: z.string(),
|
|
558
|
+
})
|
|
559
|
+
.optional(),
|
|
560
|
+
referrers: z
|
|
561
|
+
.array(z.object({
|
|
562
|
+
referrer: z.string(),
|
|
563
|
+
count: z.number(),
|
|
564
|
+
uniques: z.number(),
|
|
565
|
+
}))
|
|
566
|
+
.optional(),
|
|
567
|
+
paths: z
|
|
568
|
+
.array(z.object({
|
|
569
|
+
path: z.string(),
|
|
570
|
+
title: z.string(),
|
|
571
|
+
count: z.number(),
|
|
572
|
+
uniques: z.number(),
|
|
573
|
+
}))
|
|
574
|
+
.optional(),
|
|
575
|
+
error: z.string().optional(),
|
|
576
|
+
requiresUserInput: z.boolean().optional(),
|
|
577
|
+
instruction: z.string().optional(),
|
|
578
|
+
});
|
|
579
|
+
// ============================================================================
|
|
513
580
|
// Phase 4: Backup Tool Output Schemas
|
|
514
581
|
// ============================================================================
|
|
515
582
|
/**
|
|
@@ -673,6 +740,86 @@ const CloseGitHubIssueWithEntryOutputSchema = z.object({
|
|
|
673
740
|
requiresUserInput: z.boolean().optional(),
|
|
674
741
|
instruction: z.string().optional(),
|
|
675
742
|
});
|
|
743
|
+
// ============================================================================
|
|
744
|
+
// GitHub Milestone Output Schemas
|
|
745
|
+
// ============================================================================
|
|
746
|
+
/**
|
|
747
|
+
* GitHub milestone schema.
|
|
748
|
+
*/
|
|
749
|
+
const GitHubMilestoneOutputSchema = z.object({
|
|
750
|
+
number: z.number(),
|
|
751
|
+
title: z.string(),
|
|
752
|
+
description: z.string().nullable(),
|
|
753
|
+
state: z.enum(['open', 'closed']),
|
|
754
|
+
url: z.string(),
|
|
755
|
+
dueOn: z.string().nullable(),
|
|
756
|
+
openIssues: z.number(),
|
|
757
|
+
closedIssues: z.number(),
|
|
758
|
+
completionPercentage: z.number().optional(),
|
|
759
|
+
createdAt: z.string(),
|
|
760
|
+
updatedAt: z.string(),
|
|
761
|
+
creator: z.string().nullable(),
|
|
762
|
+
});
|
|
763
|
+
/**
|
|
764
|
+
* Schema for get_github_milestones output.
|
|
765
|
+
*/
|
|
766
|
+
const GitHubMilestonesListOutputSchema = z.object({
|
|
767
|
+
owner: z.string().optional(),
|
|
768
|
+
repo: z.string().optional(),
|
|
769
|
+
detectedOwner: z.string().nullable().optional(),
|
|
770
|
+
detectedRepo: z.string().nullable().optional(),
|
|
771
|
+
milestones: z.array(GitHubMilestoneOutputSchema).optional(),
|
|
772
|
+
count: z.number().optional(),
|
|
773
|
+
error: z.string().optional(),
|
|
774
|
+
requiresUserInput: z.boolean().optional(),
|
|
775
|
+
instruction: z.string().optional(),
|
|
776
|
+
});
|
|
777
|
+
/**
|
|
778
|
+
* Schema for get_github_milestone output.
|
|
779
|
+
*/
|
|
780
|
+
const GitHubMilestoneResultOutputSchema = z.object({
|
|
781
|
+
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
782
|
+
owner: z.string().optional(),
|
|
783
|
+
repo: z.string().optional(),
|
|
784
|
+
detectedOwner: z.string().nullable().optional(),
|
|
785
|
+
detectedRepo: z.string().nullable().optional(),
|
|
786
|
+
error: z.string().optional(),
|
|
787
|
+
requiresUserInput: z.boolean().optional(),
|
|
788
|
+
instruction: z.string().optional(),
|
|
789
|
+
});
|
|
790
|
+
/**
|
|
791
|
+
* Schema for create_github_milestone output.
|
|
792
|
+
*/
|
|
793
|
+
const CreateMilestoneOutputSchema = z.object({
|
|
794
|
+
success: z.boolean().optional(),
|
|
795
|
+
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
796
|
+
message: z.string().optional(),
|
|
797
|
+
error: z.string().optional(),
|
|
798
|
+
requiresUserInput: z.boolean().optional(),
|
|
799
|
+
instruction: z.string().optional(),
|
|
800
|
+
});
|
|
801
|
+
/**
|
|
802
|
+
* Schema for update_github_milestone output.
|
|
803
|
+
*/
|
|
804
|
+
const UpdateMilestoneOutputSchema = z.object({
|
|
805
|
+
success: z.boolean().optional(),
|
|
806
|
+
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
807
|
+
message: z.string().optional(),
|
|
808
|
+
error: z.string().optional(),
|
|
809
|
+
requiresUserInput: z.boolean().optional(),
|
|
810
|
+
instruction: z.string().optional(),
|
|
811
|
+
});
|
|
812
|
+
/**
|
|
813
|
+
* Schema for delete_github_milestone output.
|
|
814
|
+
*/
|
|
815
|
+
const DeleteMilestoneOutputSchema = z.object({
|
|
816
|
+
success: z.boolean().optional(),
|
|
817
|
+
milestoneNumber: z.number().optional(),
|
|
818
|
+
message: z.string().optional(),
|
|
819
|
+
error: z.string().optional(),
|
|
820
|
+
requiresUserInput: z.boolean().optional(),
|
|
821
|
+
instruction: z.string().optional(),
|
|
822
|
+
});
|
|
676
823
|
/**
|
|
677
824
|
* Schema for cleanup_backups output.
|
|
678
825
|
*/
|
|
@@ -791,8 +938,12 @@ function getAllToolDefinitions(context) {
|
|
|
791
938
|
if (!entry) {
|
|
792
939
|
return Promise.resolve({ error: `Entry ${entry_id} not found` });
|
|
793
940
|
}
|
|
794
|
-
const importance = db.calculateImportance(entry_id);
|
|
795
|
-
const result = {
|
|
941
|
+
const { score: importance, breakdown: importanceBreakdown } = db.calculateImportance(entry_id);
|
|
942
|
+
const result = {
|
|
943
|
+
entry,
|
|
944
|
+
importance,
|
|
945
|
+
importanceBreakdown,
|
|
946
|
+
};
|
|
796
947
|
if (include_relationships) {
|
|
797
948
|
result['relationships'] = db.getRelationships(entry_id);
|
|
798
949
|
}
|
|
@@ -1014,8 +1165,13 @@ function getAllToolDefinitions(context) {
|
|
|
1014
1165
|
`, [...sqlParams, input.min_entries]);
|
|
1015
1166
|
if (!projectsResult[0] || projectsResult[0].values.length === 0) {
|
|
1016
1167
|
return Promise.resolve({
|
|
1017
|
-
|
|
1168
|
+
project_count: 0,
|
|
1169
|
+
total_entries: 0,
|
|
1018
1170
|
projects: [],
|
|
1171
|
+
inactive_projects: [],
|
|
1172
|
+
inactiveThresholdDays: 7,
|
|
1173
|
+
time_distribution: [],
|
|
1174
|
+
message: `No projects found with at least ${String(input.min_entries)} entries`,
|
|
1019
1175
|
});
|
|
1020
1176
|
}
|
|
1021
1177
|
const columns = projectsResult[0].columns;
|
|
@@ -1074,6 +1230,7 @@ function getAllToolDefinitions(context) {
|
|
|
1074
1230
|
top_tags: projectTags[p['project_number']] ?? [],
|
|
1075
1231
|
})),
|
|
1076
1232
|
inactive_projects: inactiveProjects,
|
|
1233
|
+
inactiveThresholdDays: 7,
|
|
1077
1234
|
time_distribution: distribution,
|
|
1078
1235
|
});
|
|
1079
1236
|
},
|
|
@@ -1101,8 +1258,25 @@ function getAllToolDefinitions(context) {
|
|
|
1101
1258
|
message: 'Relationship already exists',
|
|
1102
1259
|
});
|
|
1103
1260
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1261
|
+
// P154: linkEntries throws for nonexistent entries
|
|
1262
|
+
try {
|
|
1263
|
+
const relationship = db.linkEntries(input.from_entry_id, input.to_entry_id, input.relationship_type, input.description);
|
|
1264
|
+
return Promise.resolve({ success: true, relationship });
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
return Promise.resolve({
|
|
1268
|
+
success: false,
|
|
1269
|
+
relationship: {
|
|
1270
|
+
id: 0,
|
|
1271
|
+
fromEntryId: input.from_entry_id,
|
|
1272
|
+
toEntryId: input.to_entry_id,
|
|
1273
|
+
relationshipType: input.relationship_type,
|
|
1274
|
+
description: input.description ?? null,
|
|
1275
|
+
createdAt: '',
|
|
1276
|
+
},
|
|
1277
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1106
1280
|
},
|
|
1107
1281
|
},
|
|
1108
1282
|
{
|
|
@@ -1139,6 +1313,18 @@ function getAllToolDefinitions(context) {
|
|
|
1139
1313
|
const rawDb = db.getRawDb();
|
|
1140
1314
|
let entriesResult;
|
|
1141
1315
|
if (input.entry_id !== undefined) {
|
|
1316
|
+
// P154: Pre-check entry existence to disambiguate responses
|
|
1317
|
+
const entry = db.getEntryById(input.entry_id);
|
|
1318
|
+
if (!entry) {
|
|
1319
|
+
return Promise.resolve({
|
|
1320
|
+
entry_count: 0,
|
|
1321
|
+
relationship_count: 0,
|
|
1322
|
+
root_entry: input.entry_id,
|
|
1323
|
+
depth: input.depth,
|
|
1324
|
+
mermaid: null,
|
|
1325
|
+
message: `Entry ${String(input.entry_id)} not found`,
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1142
1328
|
// Use recursive CTE to get connected entries up to depth
|
|
1143
1329
|
entriesResult = rawDb.exec(`
|
|
1144
1330
|
WITH RECURSIVE connected_entries(id, distance) AS (
|
|
@@ -1193,8 +1379,12 @@ function getAllToolDefinitions(context) {
|
|
|
1193
1379
|
}
|
|
1194
1380
|
if (!entriesResult[0] || entriesResult[0].values.length === 0) {
|
|
1195
1381
|
return Promise.resolve({
|
|
1196
|
-
|
|
1382
|
+
entry_count: 0,
|
|
1383
|
+
relationship_count: 0,
|
|
1384
|
+
root_entry: input.entry_id ?? null,
|
|
1385
|
+
depth: input.depth,
|
|
1197
1386
|
mermaid: null,
|
|
1387
|
+
message: 'No entries found with relationships matching your criteria',
|
|
1198
1388
|
});
|
|
1199
1389
|
}
|
|
1200
1390
|
// Build entries map
|
|
@@ -1352,8 +1542,17 @@ function getAllToolDefinitions(context) {
|
|
|
1352
1542
|
handler: (params) => {
|
|
1353
1543
|
const { entry_id, permanent } = DeleteEntrySchema.parse(params);
|
|
1354
1544
|
const success = db.deleteEntry(entry_id, permanent);
|
|
1545
|
+
// P154: Surface structured error for nonexistent entries
|
|
1546
|
+
if (!success) {
|
|
1547
|
+
return Promise.resolve({
|
|
1548
|
+
success: false,
|
|
1549
|
+
entryId: entry_id,
|
|
1550
|
+
permanent,
|
|
1551
|
+
error: `Entry ${String(entry_id)} not found`,
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1355
1554
|
// Remove from vector index (non-critical if fails)
|
|
1356
|
-
if (
|
|
1555
|
+
if (vectorManager) {
|
|
1357
1556
|
vectorManager.removeEntry(entry_id).catch(() => {
|
|
1358
1557
|
// Non-critical failure, entry already deleted from DB
|
|
1359
1558
|
});
|
|
@@ -1858,6 +2057,10 @@ function getAllToolDefinitions(context) {
|
|
|
1858
2057
|
body: z.string().optional().describe('Issue body/description'),
|
|
1859
2058
|
labels: z.array(z.string()).optional().describe('Labels to apply'),
|
|
1860
2059
|
assignees: z.array(z.string()).optional().describe('Users to assign'),
|
|
2060
|
+
milestone_number: z
|
|
2061
|
+
.number()
|
|
2062
|
+
.optional()
|
|
2063
|
+
.describe('Milestone number to assign this issue to'),
|
|
1861
2064
|
project_number: z
|
|
1862
2065
|
.number()
|
|
1863
2066
|
.optional()
|
|
@@ -1889,6 +2092,7 @@ function getAllToolDefinitions(context) {
|
|
|
1889
2092
|
body: z.string().optional(),
|
|
1890
2093
|
labels: z.array(z.string()).optional(),
|
|
1891
2094
|
assignees: z.array(z.string()).optional(),
|
|
2095
|
+
milestone_number: z.number().optional(),
|
|
1892
2096
|
project_number: z.number().optional(),
|
|
1893
2097
|
initial_status: z.string().optional(),
|
|
1894
2098
|
owner: z.string().optional(),
|
|
@@ -1912,7 +2116,7 @@ function getAllToolDefinitions(context) {
|
|
|
1912
2116
|
};
|
|
1913
2117
|
}
|
|
1914
2118
|
// Create the GitHub issue
|
|
1915
|
-
const issue = await github.createIssue(owner, repo, input.title, input.body, input.labels, input.assignees);
|
|
2119
|
+
const issue = await github.createIssue(owner, repo, input.title, input.body, input.labels, input.assignees, input.milestone_number);
|
|
1916
2120
|
if (!issue) {
|
|
1917
2121
|
return {
|
|
1918
2122
|
error: 'Failed to create GitHub issue. Check GITHUB_TOKEN permissions.',
|
|
@@ -2196,6 +2400,389 @@ function getAllToolDefinitions(context) {
|
|
|
2196
2400
|
};
|
|
2197
2401
|
},
|
|
2198
2402
|
},
|
|
2403
|
+
// Milestone tools
|
|
2404
|
+
{
|
|
2405
|
+
name: 'get_github_milestones',
|
|
2406
|
+
title: 'List GitHub Milestones',
|
|
2407
|
+
description: 'List GitHub milestones for the repository with completion percentages and due dates.',
|
|
2408
|
+
group: 'github',
|
|
2409
|
+
inputSchema: z.object({
|
|
2410
|
+
state: z
|
|
2411
|
+
.enum(['open', 'closed', 'all'])
|
|
2412
|
+
.optional()
|
|
2413
|
+
.default('open')
|
|
2414
|
+
.describe('Filter by state (default: open)'),
|
|
2415
|
+
limit: z
|
|
2416
|
+
.number()
|
|
2417
|
+
.optional()
|
|
2418
|
+
.default(20)
|
|
2419
|
+
.describe('Max milestones to return (default: 20)'),
|
|
2420
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2421
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2422
|
+
}),
|
|
2423
|
+
outputSchema: GitHubMilestonesListOutputSchema,
|
|
2424
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2425
|
+
handler: async (params) => {
|
|
2426
|
+
const input = z
|
|
2427
|
+
.object({
|
|
2428
|
+
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
2429
|
+
limit: z.number().optional().default(20),
|
|
2430
|
+
owner: z.string().optional(),
|
|
2431
|
+
repo: z.string().optional(),
|
|
2432
|
+
})
|
|
2433
|
+
.parse(params);
|
|
2434
|
+
if (!github) {
|
|
2435
|
+
return { error: 'GitHub integration not available' };
|
|
2436
|
+
}
|
|
2437
|
+
const repoInfo = await github.getRepoInfo();
|
|
2438
|
+
const detectedOwner = repoInfo.owner;
|
|
2439
|
+
const detectedRepo = repoInfo.repo;
|
|
2440
|
+
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
2441
|
+
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
2442
|
+
if (!owner || !repo) {
|
|
2443
|
+
return {
|
|
2444
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2445
|
+
requiresUserInput: true,
|
|
2446
|
+
detectedOwner,
|
|
2447
|
+
detectedRepo,
|
|
2448
|
+
instruction: 'Ask the user: "What GitHub repository should I list milestones for? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
const milestones = await github.getMilestones(owner, repo, input.state, input.limit);
|
|
2452
|
+
const milestonesWithPercentage = milestones.map((ms) => {
|
|
2453
|
+
const total = ms.openIssues + ms.closedIssues;
|
|
2454
|
+
const completionPercentage = total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0;
|
|
2455
|
+
return { ...ms, completionPercentage };
|
|
2456
|
+
});
|
|
2457
|
+
return {
|
|
2458
|
+
milestones: milestonesWithPercentage,
|
|
2459
|
+
count: milestonesWithPercentage.length,
|
|
2460
|
+
owner,
|
|
2461
|
+
repo,
|
|
2462
|
+
detectedOwner,
|
|
2463
|
+
detectedRepo,
|
|
2464
|
+
};
|
|
2465
|
+
},
|
|
2466
|
+
},
|
|
2467
|
+
{
|
|
2468
|
+
name: 'get_github_milestone',
|
|
2469
|
+
title: 'Get GitHub Milestone Details',
|
|
2470
|
+
description: 'Get detailed information about a specific GitHub milestone including progress and linked issue counts.',
|
|
2471
|
+
group: 'github',
|
|
2472
|
+
inputSchema: z.object({
|
|
2473
|
+
milestone_number: z.number().describe('Milestone number'),
|
|
2474
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2475
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2476
|
+
}),
|
|
2477
|
+
outputSchema: GitHubMilestoneResultOutputSchema,
|
|
2478
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2479
|
+
handler: async (params) => {
|
|
2480
|
+
const input = z
|
|
2481
|
+
.object({
|
|
2482
|
+
milestone_number: z.number(),
|
|
2483
|
+
owner: z.string().optional(),
|
|
2484
|
+
repo: z.string().optional(),
|
|
2485
|
+
})
|
|
2486
|
+
.parse(params);
|
|
2487
|
+
if (!github) {
|
|
2488
|
+
return { error: 'GitHub integration not available' };
|
|
2489
|
+
}
|
|
2490
|
+
const repoInfo = await github.getRepoInfo();
|
|
2491
|
+
const detectedOwner = repoInfo.owner;
|
|
2492
|
+
const detectedRepo = repoInfo.repo;
|
|
2493
|
+
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
2494
|
+
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
2495
|
+
if (!owner || !repo) {
|
|
2496
|
+
return {
|
|
2497
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2498
|
+
requiresUserInput: true,
|
|
2499
|
+
detectedOwner,
|
|
2500
|
+
detectedRepo,
|
|
2501
|
+
instruction: 'Ask the user: "What GitHub repository is this milestone from? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
const milestone = await github.getMilestone(owner, repo, input.milestone_number);
|
|
2505
|
+
if (!milestone) {
|
|
2506
|
+
return {
|
|
2507
|
+
error: `Milestone #${String(input.milestone_number)} not found`,
|
|
2508
|
+
owner,
|
|
2509
|
+
repo,
|
|
2510
|
+
detectedOwner,
|
|
2511
|
+
detectedRepo,
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
const total = milestone.openIssues + milestone.closedIssues;
|
|
2515
|
+
const completionPercentage = total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0;
|
|
2516
|
+
return {
|
|
2517
|
+
milestone: { ...milestone, completionPercentage },
|
|
2518
|
+
owner,
|
|
2519
|
+
repo,
|
|
2520
|
+
detectedOwner,
|
|
2521
|
+
detectedRepo,
|
|
2522
|
+
};
|
|
2523
|
+
},
|
|
2524
|
+
},
|
|
2525
|
+
{
|
|
2526
|
+
name: 'create_github_milestone',
|
|
2527
|
+
title: 'Create GitHub Milestone',
|
|
2528
|
+
description: 'Create a new GitHub milestone for tracking progress toward a project goal.',
|
|
2529
|
+
group: 'github',
|
|
2530
|
+
inputSchema: z.object({
|
|
2531
|
+
title: z.string().min(1).describe('Milestone title'),
|
|
2532
|
+
description: z.string().optional().describe('Milestone description'),
|
|
2533
|
+
due_on: z.string().optional().describe('Due date in YYYY-MM-DD format (optional)'),
|
|
2534
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2535
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2536
|
+
}),
|
|
2537
|
+
outputSchema: CreateMilestoneOutputSchema,
|
|
2538
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2539
|
+
handler: async (params) => {
|
|
2540
|
+
const input = z
|
|
2541
|
+
.object({
|
|
2542
|
+
title: z.string().min(1),
|
|
2543
|
+
description: z.string().optional(),
|
|
2544
|
+
due_on: z.string().optional(),
|
|
2545
|
+
owner: z.string().optional(),
|
|
2546
|
+
repo: z.string().optional(),
|
|
2547
|
+
})
|
|
2548
|
+
.parse(params);
|
|
2549
|
+
if (!github) {
|
|
2550
|
+
return { error: 'GitHub integration not available' };
|
|
2551
|
+
}
|
|
2552
|
+
const repoInfo = await github.getRepoInfo();
|
|
2553
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2554
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2555
|
+
if (!owner || !repo) {
|
|
2556
|
+
return {
|
|
2557
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2558
|
+
requiresUserInput: true,
|
|
2559
|
+
instruction: 'Ask the user: "What GitHub repository should I create the milestone in?"',
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
// Format due_on to ISO 8601 if provided (GitHub expects YYYY-MM-DDTHH:MM:SSZ)
|
|
2563
|
+
const dueOn = input.due_on ? `${input.due_on}T08:00:00Z` : undefined;
|
|
2564
|
+
const milestone = await github.createMilestone(owner, repo, input.title, input.description, dueOn);
|
|
2565
|
+
if (!milestone) {
|
|
2566
|
+
return {
|
|
2567
|
+
error: 'Failed to create milestone. Check GITHUB_TOKEN permissions.',
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
return {
|
|
2571
|
+
success: true,
|
|
2572
|
+
milestone: { ...milestone, completionPercentage: 0 },
|
|
2573
|
+
message: `Created milestone #${String(milestone.number)}: ${milestone.title}`,
|
|
2574
|
+
};
|
|
2575
|
+
},
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
name: 'update_github_milestone',
|
|
2579
|
+
title: 'Update GitHub Milestone',
|
|
2580
|
+
description: 'Update a GitHub milestone (title, description, due date, or state). Use state "closed" to close a completed milestone.',
|
|
2581
|
+
group: 'github',
|
|
2582
|
+
inputSchema: z.object({
|
|
2583
|
+
milestone_number: z.number().describe('Milestone number to update'),
|
|
2584
|
+
title: z.string().optional().describe('New title'),
|
|
2585
|
+
description: z.string().optional().describe('New description'),
|
|
2586
|
+
due_on: z.string().optional().describe('New due date in YYYY-MM-DD format'),
|
|
2587
|
+
state: z
|
|
2588
|
+
.enum(['open', 'closed'])
|
|
2589
|
+
.optional()
|
|
2590
|
+
.describe('Set to "closed" to close the milestone'),
|
|
2591
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2592
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2593
|
+
}),
|
|
2594
|
+
outputSchema: UpdateMilestoneOutputSchema,
|
|
2595
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2596
|
+
handler: async (params) => {
|
|
2597
|
+
const input = z
|
|
2598
|
+
.object({
|
|
2599
|
+
milestone_number: z.number(),
|
|
2600
|
+
title: z.string().optional(),
|
|
2601
|
+
description: z.string().optional(),
|
|
2602
|
+
due_on: z.string().optional(),
|
|
2603
|
+
state: z.enum(['open', 'closed']).optional(),
|
|
2604
|
+
owner: z.string().optional(),
|
|
2605
|
+
repo: z.string().optional(),
|
|
2606
|
+
})
|
|
2607
|
+
.parse(params);
|
|
2608
|
+
if (!github) {
|
|
2609
|
+
return { error: 'GitHub integration not available' };
|
|
2610
|
+
}
|
|
2611
|
+
const repoInfo = await github.getRepoInfo();
|
|
2612
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2613
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2614
|
+
if (!owner || !repo) {
|
|
2615
|
+
return {
|
|
2616
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2617
|
+
requiresUserInput: true,
|
|
2618
|
+
instruction: 'Ask the user: "What GitHub repository is this milestone in?"',
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
const dueOn = input.due_on ? `${input.due_on}T08:00:00Z` : undefined;
|
|
2622
|
+
const milestone = await github.updateMilestone(owner, repo, input.milestone_number, {
|
|
2623
|
+
title: input.title,
|
|
2624
|
+
description: input.description,
|
|
2625
|
+
dueOn,
|
|
2626
|
+
state: input.state,
|
|
2627
|
+
});
|
|
2628
|
+
if (!milestone) {
|
|
2629
|
+
return {
|
|
2630
|
+
error: `Failed to update milestone #${String(input.milestone_number)}. Check that it exists and GITHUB_TOKEN has permissions.`,
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
const total = milestone.openIssues + milestone.closedIssues;
|
|
2634
|
+
const completionPercentage = total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0;
|
|
2635
|
+
return {
|
|
2636
|
+
success: true,
|
|
2637
|
+
milestone: { ...milestone, completionPercentage },
|
|
2638
|
+
message: `Updated milestone #${String(milestone.number)}: ${milestone.title}`,
|
|
2639
|
+
};
|
|
2640
|
+
},
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
name: 'delete_github_milestone',
|
|
2644
|
+
title: 'Delete GitHub Milestone',
|
|
2645
|
+
description: 'Permanently delete a GitHub milestone. Issues assigned to the milestone will be un-assigned but not deleted.',
|
|
2646
|
+
group: 'github',
|
|
2647
|
+
inputSchema: z.object({
|
|
2648
|
+
milestone_number: z.number().describe('Milestone number to delete'),
|
|
2649
|
+
confirm: z.literal(true).describe('Must be set to true to confirm deletion'),
|
|
2650
|
+
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2651
|
+
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2652
|
+
}),
|
|
2653
|
+
outputSchema: DeleteMilestoneOutputSchema,
|
|
2654
|
+
annotations: {
|
|
2655
|
+
readOnlyHint: false,
|
|
2656
|
+
idempotentHint: false,
|
|
2657
|
+
destructiveHint: true,
|
|
2658
|
+
openWorldHint: true,
|
|
2659
|
+
},
|
|
2660
|
+
handler: async (params) => {
|
|
2661
|
+
const input = z
|
|
2662
|
+
.object({
|
|
2663
|
+
milestone_number: z.number(),
|
|
2664
|
+
confirm: z.literal(true),
|
|
2665
|
+
owner: z.string().optional(),
|
|
2666
|
+
repo: z.string().optional(),
|
|
2667
|
+
})
|
|
2668
|
+
.parse(params);
|
|
2669
|
+
if (!github) {
|
|
2670
|
+
return { error: 'GitHub integration not available' };
|
|
2671
|
+
}
|
|
2672
|
+
const repoInfo = await github.getRepoInfo();
|
|
2673
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2674
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2675
|
+
if (!owner || !repo) {
|
|
2676
|
+
return {
|
|
2677
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2678
|
+
requiresUserInput: true,
|
|
2679
|
+
instruction: 'Ask the user: "What GitHub repository is this milestone in?"',
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
const result = await github.deleteMilestone(owner, repo, input.milestone_number);
|
|
2683
|
+
if (!result.success) {
|
|
2684
|
+
return {
|
|
2685
|
+
success: false,
|
|
2686
|
+
milestoneNumber: input.milestone_number,
|
|
2687
|
+
message: `Failed to delete milestone #${String(input.milestone_number)}`,
|
|
2688
|
+
error: result.error ?? undefined,
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
return {
|
|
2692
|
+
success: true,
|
|
2693
|
+
milestoneNumber: input.milestone_number,
|
|
2694
|
+
message: `Deleted milestone #${String(input.milestone_number)}`,
|
|
2695
|
+
};
|
|
2696
|
+
},
|
|
2697
|
+
},
|
|
2698
|
+
// Repository insights tool
|
|
2699
|
+
{
|
|
2700
|
+
name: 'get_repo_insights',
|
|
2701
|
+
title: 'Repository Insights',
|
|
2702
|
+
description: '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).',
|
|
2703
|
+
group: 'github',
|
|
2704
|
+
inputSchema: z.object({
|
|
2705
|
+
sections: z
|
|
2706
|
+
.enum(['stars', 'traffic', 'referrers', 'paths', 'all'])
|
|
2707
|
+
.optional()
|
|
2708
|
+
.default('stars')
|
|
2709
|
+
.describe('Data section to return (default: stars). Use "all" for full payload.'),
|
|
2710
|
+
owner: z
|
|
2711
|
+
.string()
|
|
2712
|
+
.optional()
|
|
2713
|
+
.describe('Repository owner - LEAVE EMPTY to auto-detect'),
|
|
2714
|
+
repo: z
|
|
2715
|
+
.string()
|
|
2716
|
+
.optional()
|
|
2717
|
+
.describe('Repository name - LEAVE EMPTY to auto-detect'),
|
|
2718
|
+
}),
|
|
2719
|
+
outputSchema: RepoInsightsOutputSchema,
|
|
2720
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2721
|
+
handler: async (params) => {
|
|
2722
|
+
const input = z
|
|
2723
|
+
.object({
|
|
2724
|
+
sections: z
|
|
2725
|
+
.enum(['stars', 'traffic', 'referrers', 'paths', 'all'])
|
|
2726
|
+
.optional()
|
|
2727
|
+
.default('stars'),
|
|
2728
|
+
owner: z.string().optional(),
|
|
2729
|
+
repo: z.string().optional(),
|
|
2730
|
+
})
|
|
2731
|
+
.parse(params);
|
|
2732
|
+
if (!github) {
|
|
2733
|
+
return { error: 'GitHub integration not available' };
|
|
2734
|
+
}
|
|
2735
|
+
const repoInfo = await github.getRepoInfo();
|
|
2736
|
+
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2737
|
+
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2738
|
+
if (!owner || !repo) {
|
|
2739
|
+
return {
|
|
2740
|
+
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2741
|
+
requiresUserInput: true,
|
|
2742
|
+
instruction: 'Ask the user: "What GitHub repository should I get insights for? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
const section = input.sections;
|
|
2746
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- building response dynamically
|
|
2747
|
+
const result = {
|
|
2748
|
+
owner,
|
|
2749
|
+
repo,
|
|
2750
|
+
section,
|
|
2751
|
+
};
|
|
2752
|
+
// Stars section (default)
|
|
2753
|
+
if (section === 'stars' || section === 'all') {
|
|
2754
|
+
const stats = await github.getRepoStats(owner, repo);
|
|
2755
|
+
if (stats) {
|
|
2756
|
+
result['stars'] = stats.stars;
|
|
2757
|
+
result['forks'] = stats.forks;
|
|
2758
|
+
result['watchers'] = stats.watchers;
|
|
2759
|
+
result['openIssues'] = stats.openIssues;
|
|
2760
|
+
if (section === 'all') {
|
|
2761
|
+
result['size'] = stats.size;
|
|
2762
|
+
result['defaultBranch'] = stats.defaultBranch;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
// Traffic section
|
|
2767
|
+
if (section === 'traffic' || section === 'all') {
|
|
2768
|
+
const traffic = await github.getTrafficData(owner, repo);
|
|
2769
|
+
if (traffic) {
|
|
2770
|
+
result['traffic'] = traffic;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
// Referrers section
|
|
2774
|
+
if (section === 'referrers' || section === 'all') {
|
|
2775
|
+
const referrers = await github.getTopReferrers(owner, repo, 5);
|
|
2776
|
+
result['referrers'] = referrers;
|
|
2777
|
+
}
|
|
2778
|
+
// Paths section
|
|
2779
|
+
if (section === 'paths' || section === 'all') {
|
|
2780
|
+
const paths = await github.getPopularPaths(owner, repo, 5);
|
|
2781
|
+
result['paths'] = paths;
|
|
2782
|
+
}
|
|
2783
|
+
return result;
|
|
2784
|
+
},
|
|
2785
|
+
},
|
|
2199
2786
|
// Backup tools
|
|
2200
2787
|
{
|
|
2201
2788
|
name: 'backup_journal',
|