Skip to content

Commit 36824fc

Browse files
Add ConnectIssueGraph prompt for finding and connecting missing issue/PR relationships
This prompt provides a guided workflow for: 1. Running issue_graph to see current state 2. Searching for related issues/PRs that should be connected 3. Identifying orphaned or missing relationships 4. Proposing and adding connections with user approval 5. Verifying connections via issue_graph Parameters: - owner/repo/issue_number: The issue/PR to analyze - additional_repos: Cross-repo search (e.g., epic in planning repo) - known_links: User-provided issue URLs that should be connected
1 parent c984ad9 commit 36824fc

File tree

3 files changed

+179
-1
lines changed

3 files changed

+179
-1
lines changed

pkg/github/issue_graph.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,27 @@ var (
110110
whitespaceRegex = regexp.MustCompile(`\s+`)
111111
// HTML tags to remove
112112
htmlTagRegex = regexp.MustCompile(`<[^>]*>`)
113+
// Code block patterns to remove before extracting references
114+
fencedCodeBlockRegex = regexp.MustCompile("(?s)```[^`]*```")
115+
inlineCodeRegex = regexp.MustCompile("`[^`]+`")
113116
)
114117

118+
// stripCodeBlocks removes fenced code blocks and inline code from text
119+
// This prevents extracting issue references from example code
120+
func stripCodeBlocks(text string) string {
121+
// Remove fenced code blocks first (```...```)
122+
text = fencedCodeBlockRegex.ReplaceAllString(text, "")
123+
// Remove inline code (`...`)
124+
text = inlineCodeRegex.ReplaceAllString(text, "")
125+
return text
126+
}
127+
115128
// extractIssueReferences extracts all issue/PR references from text
129+
// It strips code blocks first to avoid picking up example references
116130
func extractIssueReferences(text, defaultOwner, defaultRepo string) []IssueReference {
131+
// Strip code blocks to avoid extracting references from examples
132+
text = stripCodeBlocks(text)
133+
117134
refs := make([]IssueReference, 0)
118135
seen := make(map[string]bool)
119136

@@ -495,6 +512,11 @@ func (gc *graphCrawler) crawl(ctx context.Context) error {
495512

496513
refKey := nodeKey(ref.Owner, ref.Repo, ref.Number)
497514

515+
// Skip self-references
516+
if refKey == key {
517+
continue
518+
}
519+
498520
// Determine relationship
499521
relType := RelationTypeRelated
500522
if ref.IsParent {
@@ -629,6 +651,14 @@ func (gc *graphCrawler) crawl(ctx context.Context) error {
629651
return nil
630652
}
631653

654+
// edgeKey creates a unique key for an edge to enable deduplication
655+
func edgeKey(e GraphEdge) string {
656+
return fmt.Sprintf("%s/%s#%d->%s/%s#%d:%s",
657+
strings.ToLower(e.FromOwner), strings.ToLower(e.FromRepo), e.FromNumber,
658+
strings.ToLower(e.ToOwner), strings.ToLower(e.ToRepo), e.ToNumber,
659+
e.Relation)
660+
}
661+
632662
// buildGraph constructs the final IssueGraph
633663
func (gc *graphCrawler) buildGraph() *IssueGraph {
634664
gc.mu.RLock()
@@ -648,12 +678,23 @@ func (gc *graphCrawler) buildGraph() *IssueGraph {
648678
return nodes[i].Number < nodes[j].Number
649679
})
650680

681+
// Deduplicate edges
682+
seenEdges := make(map[string]bool)
683+
uniqueEdges := make([]GraphEdge, 0, len(gc.edges))
684+
for _, edge := range gc.edges {
685+
key := edgeKey(edge)
686+
if !seenEdges[key] {
687+
seenEdges[key] = true
688+
uniqueEdges = append(uniqueEdges, edge)
689+
}
690+
}
691+
651692
return &IssueGraph{
652693
FocusOwner: gc.focusOwner,
653694
FocusRepo: gc.focusRepo,
654695
FocusNumber: gc.focusNumber,
655696
Nodes: nodes,
656-
Edges: gc.edges,
697+
Edges: uniqueEdges,
657698
Summary: gc.generateSummary(),
658699
}
659700
}

pkg/github/issue_graph_prompts.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/github/github-mcp-server/pkg/translations"
8+
"github.com/mark3labs/mcp-go/mcp"
9+
"github.com/mark3labs/mcp-go/server"
10+
)
11+
12+
// ConnectIssueGraphPrompt provides a guided workflow for finding and connecting missing relationships
13+
// between issues and PRs in the issue graph
14+
func ConnectIssueGraphPrompt(t translations.TranslationHelperFunc) (tool mcp.Prompt, handler server.PromptHandlerFunc) {
15+
return mcp.NewPrompt("ConnectIssueGraph",
16+
mcp.WithPromptDescription(t("PROMPT_CONNECT_ISSUE_GRAPH_DESCRIPTION", "Find and connect missing relationships between issues and PRs in the issue graph")),
17+
mcp.WithArgument("owner", mcp.ArgumentDescription("Repository owner"), mcp.RequiredArgument()),
18+
mcp.WithArgument("repo", mcp.ArgumentDescription("Repository name"), mcp.RequiredArgument()),
19+
mcp.WithArgument("issue_number", mcp.ArgumentDescription("Issue or PR number to analyze"), mcp.RequiredArgument()),
20+
mcp.WithArgument("additional_repos", mcp.ArgumentDescription("Comma-separated list of additional owner/repo to search (e.g., 'github/copilot,microsoft/vscode')")),
21+
mcp.WithArgument("known_links", mcp.ArgumentDescription("Comma-separated list of known related issue URLs that should be connected (e.g., epic links in other repos)")),
22+
), func(_ context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
23+
owner := request.Params.Arguments["owner"]
24+
repo := request.Params.Arguments["repo"]
25+
issueNumber := request.Params.Arguments["issue_number"]
26+
27+
additionalRepos := ""
28+
if r, exists := request.Params.Arguments["additional_repos"]; exists {
29+
additionalRepos = fmt.Sprintf("%v", r)
30+
}
31+
32+
knownLinks := ""
33+
if k, exists := request.Params.Arguments["known_links"]; exists {
34+
knownLinks = fmt.Sprintf("%v", k)
35+
}
36+
37+
systemPrompt := `You are a GitHub issue graph connection assistant. Your job is to find missing relationships between issues and PRs and help connect them properly.
38+
39+
WORKFLOW:
40+
1. First, use issue_graph tool on the specified issue/PR to see current relationships
41+
2. Search for potentially related issues and PRs using search_issues and search_pull_requests
42+
3. Identify missing connections (orphaned tasks, PRs without issue links, etc.)
43+
4. For each missing connection, help the user add the appropriate reference
44+
5. Verify the connections by running issue_graph again
45+
46+
RELATIONSHIP TYPES TO LOOK FOR:
47+
- Epic → Batch: Large initiatives broken into batches
48+
- Batch → Task: Parent issues with sub-issues
49+
- Task → PR: Issues with PRs that should "close" them
50+
- Cross-repo: Epics/batches in different repos (e.g., planning repo vs implementation repo)
51+
52+
SEARCH STRATEGIES:
53+
- Search by keywords from the issue title
54+
- Search by feature name or component
55+
- Look for PRs that mention the issue number
56+
- Check for issues with similar labels
57+
58+
ADDING CONNECTIONS:
59+
- PRs should reference issues with "Closes #123" or "Fixes #123" in body
60+
- Cross-repo: "Closes owner/repo#123"
61+
- Sub-issues can be added via the sub_issue_write tool
62+
- Issue bodies can reference related work with "Related to #123"
63+
64+
IMPORTANT:
65+
- Ask the user before making any changes
66+
- Cross-repo epics may need user input (they might not be searchable)
67+
- Some relationships are intentionally loose - confirm with user`
68+
69+
userPrompt := fmt.Sprintf(`I want to analyze and connect the issue graph for %s/%s#%s.
70+
71+
Please:
72+
1. Run issue_graph on this issue/PR to see current state
73+
2. Search for related issues and PRs that should be connected
74+
3. Identify any orphaned or missing relationships
75+
4. Propose specific connections to add
76+
5. After I approve changes, help me add the connections
77+
6. Verify by running issue_graph again`, owner, repo, issueNumber)
78+
79+
if additionalRepos != "" {
80+
userPrompt += fmt.Sprintf(`
81+
82+
Also search these additional repositories for related issues/PRs:
83+
%s`, additionalRepos)
84+
}
85+
86+
if knownLinks != "" {
87+
userPrompt += fmt.Sprintf(`
88+
89+
These are known related issues that should be connected (e.g., epics in other repos):
90+
%s
91+
92+
Please verify these are properly referenced and suggest how to connect them if not.`, knownLinks)
93+
}
94+
95+
messages := []mcp.PromptMessage{
96+
{
97+
Role: "user",
98+
Content: mcp.NewTextContent(systemPrompt),
99+
},
100+
{
101+
Role: "user",
102+
Content: mcp.NewTextContent(userPrompt),
103+
},
104+
{
105+
Role: "assistant",
106+
Content: mcp.NewTextContent(fmt.Sprintf(`I'll help you analyze and connect the issue graph for %s/%s#%s.
107+
108+
Let me start by running the issue_graph tool to see the current state of relationships:
109+
110+
**Step 1: Analyze Current Graph**
111+
I'll call issue_graph to see what connections already exist.
112+
113+
**Step 2: Search for Missing Connections**
114+
Then I'll search for:
115+
- PRs that might close this issue but don't reference it
116+
- Related issues that should be sub-issues or parent issues
117+
- Cross-repo references that might be missing
118+
119+
**Step 3: Propose Connections**
120+
I'll list any missing relationships I find and propose how to connect them.
121+
122+
**Step 4: Make Changes (with your approval)**
123+
After you review, I'll help add the connections.
124+
125+
**Step 5: Verify**
126+
Finally, I'll run issue_graph again to confirm the connections.
127+
128+
Let me start by getting the current issue graph...`, owner, repo, issueNumber)),
129+
},
130+
}
131+
132+
return &mcp.GetPromptResult{
133+
Messages: messages,
134+
}, nil
135+
}
136+
}

pkg/github/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
215215
).AddPrompts(
216216
toolsets.NewServerPrompt(AssignCodingAgentPrompt(t)),
217217
toolsets.NewServerPrompt(IssueToFixWorkflowPrompt(t)),
218+
toolsets.NewServerPrompt(ConnectIssueGraphPrompt(t)),
218219
)
219220
users := toolsets.NewToolset(ToolsetMetadataUsers.ID, ToolsetMetadataUsers.Description).
220221
AddReadTools(

0 commit comments

Comments
 (0)