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
|
@@ -572,6 +572,7 @@ ${entrySummary}
|
|
|
572
572
|
**For More Context:**
|
|
573
573
|
- Full entries: \`memory://recent\` or \`get_entry_by_id(ID)\`
|
|
574
574
|
- GitHub status: \`memory://github/status\`
|
|
575
|
+
- Repo insights: \`memory://github/insights\` (stars, traffic, clones)
|
|
575
576
|
- Full health: \`memory://health\`
|
|
576
577
|
|
|
577
578
|
Please confirm this context to the user in a concise, friendly format. Use a table if helpful.`,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { SqliteAdapter } from '../../database/SqliteAdapter.js'
|
|
8
8
|
import type { VectorSearchManager } from '../../vector/VectorSearchManager.js'
|
|
9
9
|
import type { ToolFilterConfig } from '../../filtering/ToolFilter.js'
|
|
10
|
+
import { getAllToolNames } from '../../filtering/ToolFilter.js'
|
|
10
11
|
import type { Tag, McpIcon } from '../../types/index.js'
|
|
11
12
|
import type { GitHubIntegration } from '../../github/GitHubIntegration.js'
|
|
12
13
|
import { generateInstructions, type InstructionLevel } from '../../constants/ServerInstructions.js'
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
ICON_GRAPH,
|
|
18
19
|
ICON_HEALTH,
|
|
19
20
|
ICON_GITHUB,
|
|
21
|
+
ICON_MILESTONE,
|
|
20
22
|
ICON_STAR,
|
|
21
23
|
ICON_TAG,
|
|
22
24
|
ICON_TEAM,
|
|
@@ -192,8 +194,7 @@ function execQuery(
|
|
|
192
194
|
* Get total tool count for health status
|
|
193
195
|
*/
|
|
194
196
|
function getTotalToolCount(): number {
|
|
195
|
-
|
|
196
|
-
return 33 // 6 core + 4 search + 2 analytics + 2 relationships + 1 export + 5 admin + 9 github + 4 backup
|
|
197
|
+
return getAllToolNames().length
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
/**
|
|
@@ -261,6 +262,13 @@ function getAllResourceDefinitions(): InternalResourceDef[] {
|
|
|
261
262
|
ci: 'passing' | 'failing' | 'pending' | 'cancelled' | 'unknown'
|
|
262
263
|
openIssues: number
|
|
263
264
|
openPRs: number
|
|
265
|
+
milestones: { title: string; progress: string; dueOn: string | null }[]
|
|
266
|
+
insights?: {
|
|
267
|
+
stars: number | null
|
|
268
|
+
forks: number | null
|
|
269
|
+
clones14d?: number
|
|
270
|
+
views14d?: number
|
|
271
|
+
}
|
|
264
272
|
} | null = null
|
|
265
273
|
|
|
266
274
|
if (context.github) {
|
|
@@ -328,12 +336,75 @@ function getAllResourceDefinitions(): InternalResourceDef[] {
|
|
|
328
336
|
// Counts unavailable
|
|
329
337
|
}
|
|
330
338
|
|
|
339
|
+
// Get milestone summary for briefing
|
|
340
|
+
let milestones: {
|
|
341
|
+
title: string
|
|
342
|
+
progress: string
|
|
343
|
+
dueOn: string | null
|
|
344
|
+
}[] = []
|
|
345
|
+
try {
|
|
346
|
+
const msList = await context.github.getMilestones(
|
|
347
|
+
owner,
|
|
348
|
+
repo,
|
|
349
|
+
'open',
|
|
350
|
+
3
|
|
351
|
+
)
|
|
352
|
+
milestones = msList.map((m) => {
|
|
353
|
+
const total = m.closedIssues + m.openIssues
|
|
354
|
+
const pct =
|
|
355
|
+
total > 0 ? Math.round((m.closedIssues / total) * 100) : 0
|
|
356
|
+
return {
|
|
357
|
+
title: m.title,
|
|
358
|
+
progress: `${String(pct)}%`,
|
|
359
|
+
dueOn: m.dueOn,
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
} catch {
|
|
363
|
+
// Milestones unavailable
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Get repo insights (stars, forks, traffic)
|
|
367
|
+
let insights:
|
|
368
|
+
| {
|
|
369
|
+
stars: number | null
|
|
370
|
+
forks: number | null
|
|
371
|
+
clones14d?: number
|
|
372
|
+
views14d?: number
|
|
373
|
+
}
|
|
374
|
+
| undefined = undefined
|
|
375
|
+
try {
|
|
376
|
+
const repoStats = await context.github.getRepoStats(owner, repo)
|
|
377
|
+
if (repoStats) {
|
|
378
|
+
insights = {
|
|
379
|
+
stars: repoStats.stars ?? null,
|
|
380
|
+
forks: repoStats.forks ?? null,
|
|
381
|
+
}
|
|
382
|
+
// Traffic requires push access - may fail
|
|
383
|
+
try {
|
|
384
|
+
const trafficData = await context.github.getTrafficData(
|
|
385
|
+
owner,
|
|
386
|
+
repo
|
|
387
|
+
)
|
|
388
|
+
if (trafficData) {
|
|
389
|
+
insights.clones14d = trafficData.clones.total
|
|
390
|
+
insights.views14d = trafficData.views.total
|
|
391
|
+
}
|
|
392
|
+
} catch {
|
|
393
|
+
// Traffic data unavailable (requires push access)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
// Repo stats unavailable
|
|
398
|
+
}
|
|
399
|
+
|
|
331
400
|
github = {
|
|
332
401
|
repo: `${owner}/${repo}`,
|
|
333
402
|
branch: repoInfo.branch ?? null,
|
|
334
403
|
ci: ciStatus,
|
|
335
404
|
openIssues,
|
|
336
405
|
openPRs,
|
|
406
|
+
milestones,
|
|
407
|
+
insights,
|
|
337
408
|
}
|
|
338
409
|
}
|
|
339
410
|
} catch {
|
|
@@ -356,6 +427,29 @@ function getAllResourceDefinitions(): InternalResourceDef[] {
|
|
|
356
427
|
? `#${latestEntries[0].id} (${latestEntries[0].type}): ${latestEntries[0].preview}`
|
|
357
428
|
: 'No entries yet'
|
|
358
429
|
|
|
430
|
+
const milestoneRow =
|
|
431
|
+
github?.milestones && github.milestones.length > 0
|
|
432
|
+
? `\n| **Milestones** | ${github.milestones.map((m) => `${m.title} (${m.progress}${m.dueOn ? `, due ${m.dueOn.split('T')[0] ?? ''}` : ''})`).join(', ')} |`
|
|
433
|
+
: ''
|
|
434
|
+
|
|
435
|
+
// Build insights row for userMessage
|
|
436
|
+
let insightsRow = ''
|
|
437
|
+
if (github?.insights) {
|
|
438
|
+
const parts: string[] = []
|
|
439
|
+
if (github.insights.stars !== null)
|
|
440
|
+
parts.push(`⭐ ${String(github.insights.stars)} stars`)
|
|
441
|
+
if (github.insights.forks !== null)
|
|
442
|
+
parts.push(`🍴 ${String(github.insights.forks)} forks`)
|
|
443
|
+
if (github.insights.clones14d !== undefined)
|
|
444
|
+
parts.push(`📦 ${String(github.insights.clones14d)} clones`)
|
|
445
|
+
if (github.insights.views14d !== undefined)
|
|
446
|
+
parts.push(`👁️ ${String(github.insights.views14d)} views`)
|
|
447
|
+
if (parts.length > 0) {
|
|
448
|
+
const trafficNote = github.insights.clones14d !== undefined ? ' (14d)' : ''
|
|
449
|
+
insightsRow = `\n| **Insights** | ${parts.join(' · ')}${trafficNote} |`
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
359
453
|
return {
|
|
360
454
|
data: {
|
|
361
455
|
version: pkg.version,
|
|
@@ -377,11 +471,13 @@ function getAllResourceDefinitions(): InternalResourceDef[] {
|
|
|
377
471
|
'memory://prs/{pr_number}/timeline',
|
|
378
472
|
'memory://kanban/{project_number}',
|
|
379
473
|
'memory://kanban/{project_number}/diagram',
|
|
474
|
+
'memory://milestones/{number}',
|
|
380
475
|
],
|
|
381
476
|
more: {
|
|
382
477
|
fullHealth: 'memory://health',
|
|
383
478
|
allRecent: 'memory://recent',
|
|
384
479
|
githubStatus: 'memory://github/status',
|
|
480
|
+
repoInsights: 'memory://github/insights',
|
|
385
481
|
contextBundle: 'get-context-bundle prompt',
|
|
386
482
|
},
|
|
387
483
|
// IMPORTANT: Agent should relay this message to the user
|
|
@@ -392,7 +488,7 @@ function getAllResourceDefinitions(): InternalResourceDef[] {
|
|
|
392
488
|
| **Branch** | ${branchName} |
|
|
393
489
|
| **CI Status** | ${ciStatus} |
|
|
394
490
|
| **Journal** | ${totalEntries} entries |
|
|
395
|
-
| **Latest** | ${latestPreview}
|
|
491
|
+
| **Latest** | ${latestPreview} |${milestoneRow}${insightsRow}
|
|
396
492
|
|
|
397
493
|
I have project memory access and will create entries for significant work.`,
|
|
398
494
|
// Note for clients that don't auto-inject ServerInstructions
|
|
@@ -420,8 +516,9 @@ I have project memory access and will create entries for significant work.`,
|
|
|
420
516
|
// because the MCP SDK performs exact URI matching before calling handlers.
|
|
421
517
|
const level: InstructionLevel = 'full'
|
|
422
518
|
|
|
423
|
-
// Get enabled tools from filter config or all
|
|
424
|
-
const
|
|
519
|
+
// Get enabled tools from filter config, or fall back to all tool names
|
|
520
|
+
const allToolNames = new Set(getAllToolNames())
|
|
521
|
+
const enabledTools = context.filterConfig?.enabledTools ?? allToolNames
|
|
425
522
|
|
|
426
523
|
// Get prompts for instruction generation
|
|
427
524
|
const prompts = getPrompts().map((p) => {
|
|
@@ -437,9 +534,7 @@ I have project memory access and will create entries for significant work.`,
|
|
|
437
534
|
|
|
438
535
|
// Generate instructions at requested level
|
|
439
536
|
const instructions = generateInstructions(
|
|
440
|
-
enabledTools
|
|
441
|
-
? enabledTools
|
|
442
|
-
: new Set(['create_entry', 'search_entries', 'get_recent_entries']),
|
|
537
|
+
enabledTools,
|
|
443
538
|
resources,
|
|
444
539
|
prompts,
|
|
445
540
|
undefined, // No latest entry needed for instructions
|
|
@@ -483,21 +578,23 @@ I have project memory access and will create entries for significant work.`,
|
|
|
483
578
|
priority: 0.7,
|
|
484
579
|
},
|
|
485
580
|
handler: (_uri: string, context: ResourceContext) => {
|
|
581
|
+
// Fetch ALL significant entries so importance sort runs on the full set
|
|
582
|
+
// (not just the 20 most recent). We then slice after sorting.
|
|
486
583
|
const rows = execQuery(
|
|
487
584
|
context.db,
|
|
488
585
|
`
|
|
489
586
|
SELECT * FROM memory_journal
|
|
490
587
|
WHERE significance_type IS NOT NULL
|
|
491
588
|
AND deleted_at IS NULL
|
|
492
|
-
ORDER BY timestamp DESC
|
|
493
|
-
LIMIT 20
|
|
494
589
|
`
|
|
495
590
|
)
|
|
496
591
|
// Transform entries and calculate importance scores
|
|
497
592
|
const entriesWithImportance: (Record<string, unknown> & { importance: number })[] =
|
|
498
593
|
rows.map((row) => {
|
|
499
594
|
const entry = transformEntryRow(row)
|
|
500
|
-
const importance = context.db.calculateImportance(
|
|
595
|
+
const { score: importance } = context.db.calculateImportance(
|
|
596
|
+
entry['id'] as number
|
|
597
|
+
)
|
|
501
598
|
return { ...entry, importance }
|
|
502
599
|
})
|
|
503
600
|
// Sort by importance (highest first), then by timestamp (newest first) for ties
|
|
@@ -510,7 +607,9 @@ I have project memory access and will create entries for significant work.`,
|
|
|
510
607
|
const bTime = new Date(b['timestamp'] as string).getTime()
|
|
511
608
|
return bTime - aTime
|
|
512
609
|
})
|
|
513
|
-
|
|
610
|
+
// Slice to top 20 AFTER sorting (not before) to ensure correctness
|
|
611
|
+
const top20 = entriesWithImportance.slice(0, 20)
|
|
612
|
+
return { entries: top20, count: top20.length }
|
|
514
613
|
},
|
|
515
614
|
},
|
|
516
615
|
{
|
|
@@ -1198,6 +1297,39 @@ I have project memory access and will create entries for significant work.`,
|
|
|
1198
1297
|
// Kanban not available
|
|
1199
1298
|
}
|
|
1200
1299
|
|
|
1300
|
+
// Get milestone summary
|
|
1301
|
+
let milestoneSummary:
|
|
1302
|
+
| {
|
|
1303
|
+
number: number
|
|
1304
|
+
title: string
|
|
1305
|
+
state: string
|
|
1306
|
+
openIssues: number
|
|
1307
|
+
closedIssues: number
|
|
1308
|
+
completionPercentage: number
|
|
1309
|
+
dueOn: string | null
|
|
1310
|
+
}[]
|
|
1311
|
+
| null = null
|
|
1312
|
+
try {
|
|
1313
|
+
const milestones = await context.github.getMilestones(owner, repo, 'open', 5)
|
|
1314
|
+
if (milestones.length > 0) {
|
|
1315
|
+
milestoneSummary = milestones.map((ms) => {
|
|
1316
|
+
const total = ms.openIssues + ms.closedIssues
|
|
1317
|
+
const pct = total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0
|
|
1318
|
+
return {
|
|
1319
|
+
number: ms.number,
|
|
1320
|
+
title: ms.title,
|
|
1321
|
+
state: ms.state,
|
|
1322
|
+
openIssues: ms.openIssues,
|
|
1323
|
+
closedIssues: ms.closedIssues,
|
|
1324
|
+
completionPercentage: pct,
|
|
1325
|
+
dueOn: ms.dueOn,
|
|
1326
|
+
}
|
|
1327
|
+
})
|
|
1328
|
+
}
|
|
1329
|
+
} catch {
|
|
1330
|
+
// Milestones not available
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1201
1333
|
return {
|
|
1202
1334
|
data: {
|
|
1203
1335
|
repository: `${owner}/${repo}`,
|
|
@@ -1216,11 +1348,185 @@ I have project memory access and will create entries for significant work.`,
|
|
|
1216
1348
|
items: openPrs,
|
|
1217
1349
|
},
|
|
1218
1350
|
kanbanSummary,
|
|
1351
|
+
milestones: milestoneSummary,
|
|
1352
|
+
},
|
|
1353
|
+
annotations: { lastModified },
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
},
|
|
1357
|
+
// Repository insights resource
|
|
1358
|
+
{
|
|
1359
|
+
uri: 'memory://github/insights',
|
|
1360
|
+
name: 'Repository Insights',
|
|
1361
|
+
title: 'Repository Stars & Traffic Summary',
|
|
1362
|
+
description: 'Compact repo insights: stars, forks, 14-day traffic totals (~150 tokens)',
|
|
1363
|
+
mimeType: 'application/json',
|
|
1364
|
+
icons: [ICON_ANALYTICS],
|
|
1365
|
+
annotations: {
|
|
1366
|
+
audience: ['assistant'],
|
|
1367
|
+
priority: 0.4, // Lower than status — optional enrichment
|
|
1368
|
+
},
|
|
1369
|
+
handler: async (_uri: string, context: ResourceContext): Promise<ResourceResult> => {
|
|
1370
|
+
const lastModified = new Date().toISOString()
|
|
1371
|
+
|
|
1372
|
+
if (!context.github) {
|
|
1373
|
+
return {
|
|
1374
|
+
data: {
|
|
1375
|
+
error: 'GitHub integration not available',
|
|
1376
|
+
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1377
|
+
},
|
|
1378
|
+
annotations: { lastModified },
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
const repoInfo = await context.github.getRepoInfo()
|
|
1383
|
+
const owner = repoInfo.owner
|
|
1384
|
+
const repo = repoInfo.repo
|
|
1385
|
+
|
|
1386
|
+
if (!owner || !repo) {
|
|
1387
|
+
return {
|
|
1388
|
+
data: {
|
|
1389
|
+
error: 'Could not detect repository',
|
|
1390
|
+
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1391
|
+
},
|
|
1392
|
+
annotations: { lastModified },
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Get repo stats (stars, forks)
|
|
1397
|
+
const stats = await context.github.getRepoStats(owner, repo)
|
|
1398
|
+
|
|
1399
|
+
// Get traffic data (clones, views) - may fail if token lacks push access
|
|
1400
|
+
let traffic: { clones14d: number; views14d: number } | null = null
|
|
1401
|
+
try {
|
|
1402
|
+
const trafficData = await context.github.getTrafficData(owner, repo)
|
|
1403
|
+
if (trafficData) {
|
|
1404
|
+
traffic = {
|
|
1405
|
+
clones14d: trafficData.clones.total,
|
|
1406
|
+
views14d: trafficData.views.total,
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
} catch {
|
|
1410
|
+
// Traffic data requires push access - silently skip
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
return {
|
|
1414
|
+
data: {
|
|
1415
|
+
repository: `${owner}/${repo}`,
|
|
1416
|
+
stars: stats?.stars ?? null,
|
|
1417
|
+
forks: stats?.forks ?? null,
|
|
1418
|
+
watchers: stats?.watchers ?? null,
|
|
1419
|
+
...(traffic ?? {}),
|
|
1420
|
+
hint: !traffic
|
|
1421
|
+
? 'Traffic data requires push access to the repository.'
|
|
1422
|
+
: undefined,
|
|
1219
1423
|
},
|
|
1220
1424
|
annotations: { lastModified },
|
|
1221
1425
|
}
|
|
1222
1426
|
},
|
|
1223
1427
|
},
|
|
1428
|
+
// Milestone resources
|
|
1429
|
+
{
|
|
1430
|
+
uri: 'memory://github/milestones',
|
|
1431
|
+
name: 'GitHub Milestones',
|
|
1432
|
+
title: 'GitHub Repository Milestones',
|
|
1433
|
+
description:
|
|
1434
|
+
'Open GitHub milestones with completion percentages, due dates, and issue counts',
|
|
1435
|
+
mimeType: 'application/json',
|
|
1436
|
+
icons: [ICON_MILESTONE],
|
|
1437
|
+
annotations: {
|
|
1438
|
+
audience: ['assistant'],
|
|
1439
|
+
priority: 0.6,
|
|
1440
|
+
},
|
|
1441
|
+
handler: async (_uri: string, context: ResourceContext) => {
|
|
1442
|
+
if (!context.github) {
|
|
1443
|
+
return {
|
|
1444
|
+
error: 'GitHub integration not available',
|
|
1445
|
+
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const repoInfo = await context.github.getRepoInfo()
|
|
1450
|
+
const owner = repoInfo.owner
|
|
1451
|
+
const repo = repoInfo.repo
|
|
1452
|
+
|
|
1453
|
+
if (!owner || !repo) {
|
|
1454
|
+
return {
|
|
1455
|
+
error: 'Could not detect repository',
|
|
1456
|
+
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const milestones = await context.github.getMilestones(owner, repo, 'open', 20)
|
|
1461
|
+
const milestonesWithProgress = milestones.map((ms) => {
|
|
1462
|
+
const total = ms.openIssues + ms.closedIssues
|
|
1463
|
+
const completionPercentage =
|
|
1464
|
+
total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0
|
|
1465
|
+
return { ...ms, completionPercentage }
|
|
1466
|
+
})
|
|
1467
|
+
|
|
1468
|
+
return {
|
|
1469
|
+
repository: `${owner}/${repo}`,
|
|
1470
|
+
milestones: milestonesWithProgress,
|
|
1471
|
+
count: milestonesWithProgress.length,
|
|
1472
|
+
hint: 'Use get_github_milestones tool for state filtering. Use memory://milestones/{number} for detail.',
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
uri: 'memory://milestones/{number}',
|
|
1478
|
+
name: 'Milestone Detail',
|
|
1479
|
+
title: 'GitHub Milestone Detail',
|
|
1480
|
+
description:
|
|
1481
|
+
'Detailed view of a single GitHub milestone with completion progress and issue counts. Use get_github_issues with the milestone filter for individual issue details.',
|
|
1482
|
+
mimeType: 'application/json',
|
|
1483
|
+
icons: [ICON_MILESTONE],
|
|
1484
|
+
annotations: {
|
|
1485
|
+
audience: ['assistant'],
|
|
1486
|
+
priority: 0.5,
|
|
1487
|
+
},
|
|
1488
|
+
handler: async (uri: string, context: ResourceContext) => {
|
|
1489
|
+
const match = /memory:\/\/milestones\/(\d+)/.exec(uri)
|
|
1490
|
+
const milestoneNumber = match?.[1] ? parseInt(match[1], 10) : null
|
|
1491
|
+
|
|
1492
|
+
if (milestoneNumber === null) {
|
|
1493
|
+
return { error: 'Invalid milestone number' }
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (!context.github) {
|
|
1497
|
+
return {
|
|
1498
|
+
error: 'GitHub integration not available',
|
|
1499
|
+
hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
const repoInfo = await context.github.getRepoInfo()
|
|
1504
|
+
const owner = repoInfo.owner
|
|
1505
|
+
const repo = repoInfo.repo
|
|
1506
|
+
|
|
1507
|
+
if (!owner || !repo) {
|
|
1508
|
+
return {
|
|
1509
|
+
error: 'Could not detect repository',
|
|
1510
|
+
hint: 'Set GITHUB_REPO_PATH to your git repository.',
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
const milestone = await context.github.getMilestone(owner, repo, milestoneNumber)
|
|
1515
|
+
if (!milestone) {
|
|
1516
|
+
return { error: `Milestone #${String(milestoneNumber)} not found` }
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const total = milestone.openIssues + milestone.closedIssues
|
|
1520
|
+
const completionPercentage =
|
|
1521
|
+
total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0
|
|
1522
|
+
|
|
1523
|
+
return {
|
|
1524
|
+
repository: `${owner}/${repo}`,
|
|
1525
|
+
milestone: { ...milestone, completionPercentage },
|
|
1526
|
+
hint: 'Use get_github_issues tool to list issues associated with this milestone.',
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1224
1530
|
// Kanban board resources (GitHub Projects v2)
|
|
1225
1531
|
{
|
|
1226
1532
|
uri: 'memory://kanban/{project_number}',
|