Slice 5: Gitea forge - PRs and Merge & clean up
New internal/forge (provider-abstracted, Gitea impl via code.gitea.io/sdk/gitea) with tested remote-URL parsing and read+write ops: list open PRs, and merge-and-cleanup (squash-merge + delete head branch when head/base share a repo). Service resolves repo->owner/repo from remotes (prefers origin) and records a pr-merged event; config gains GITEA_URL/GITEA_TOKEN (forge disabled without both). MCP tools list_prs and merge_and_cleanup_pr (merge tool tells Claude to confirm first, 1.4). HTTP GET /api/repo/prs, POST /api/repo/pr/merge. New <pr-list> component with a confirming Merge & clean up button, hidden when no forge. Verified graceful-disabled path; real merge pending token + a designated PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"gitmanager/internal/activity"
|
||||
"gitmanager/internal/forge"
|
||||
"gitmanager/internal/repos"
|
||||
"gitmanager/internal/service"
|
||||
)
|
||||
@@ -64,6 +65,22 @@ type ackSwitchOutput struct {
|
||||
Target string `json:"target,omitempty" jsonschema:"the repository now active"`
|
||||
}
|
||||
|
||||
// repoPathInput selects a repository by path (from list_repos).
|
||||
type repoPathInput struct {
|
||||
Path string `json:"path" jsonschema:"absolute path of the repository, exactly as returned by list_repos"`
|
||||
}
|
||||
|
||||
// prListOutput wraps the pull requests (object, per the schema rule).
|
||||
type prListOutput struct {
|
||||
PRs []forge.PullRequest `json:"prs" jsonschema:"open pull requests for the repository"`
|
||||
}
|
||||
|
||||
// mergePRInput selects the PR to merge and clean up.
|
||||
type mergePRInput struct {
|
||||
Path string `json:"path" jsonschema:"absolute path of the repository, from list_repos"`
|
||||
Number int64 `json:"number" jsonschema:"the pull request number to merge and clean up"`
|
||||
}
|
||||
|
||||
// NewServer builds the MCP server and registers the (currently read-only) tools.
|
||||
func NewServer(svc *service.Service, version string) *mcpsdk.Server {
|
||||
s := mcpsdk.NewServer(&mcpsdk.Implementation{
|
||||
@@ -144,6 +161,30 @@ func NewServer(svc *service.Service, version string) *mcpsdk.Server {
|
||||
return nil, ackSwitchOutput{Switched: true, Target: p.Target}, nil
|
||||
})
|
||||
|
||||
// list_prs — open pull requests for a repo (§8.4).
|
||||
mcpsdk.AddTool(s, &mcpsdk.Tool{
|
||||
Name: "list_prs",
|
||||
Description: "List the open pull requests for a repository (needs a configured forge such as Gitea). The path must be one returned by list_repos.",
|
||||
}, func(ctx context.Context, _ *mcpsdk.CallToolRequest, in repoPathInput) (*mcpsdk.CallToolResult, prListOutput, error) {
|
||||
prs, err := svc.ForgePRs(ctx, in.Path)
|
||||
if err != nil {
|
||||
return nil, prListOutput{}, err
|
||||
}
|
||||
return nil, prListOutput{PRs: prs}, nil
|
||||
})
|
||||
|
||||
// merge_and_cleanup_pr — DESTRUCTIVE: merges a PR and deletes its branch.
|
||||
mcpsdk.AddTool(s, &mcpsdk.Tool{
|
||||
Name: "merge_and_cleanup_pr",
|
||||
Description: "Merge a pull request (squash) AND delete its source branch — the \"Merge & clean up\" action. This is irreversible: confirm the exact PR number and repository with the user BEFORE calling. Merged history remains on the host; only the branch is removed.",
|
||||
}, func(ctx context.Context, _ *mcpsdk.CallToolRequest, in mergePRInput) (*mcpsdk.CallToolResult, forge.MergeResult, error) {
|
||||
res, err := svc.MergeAndCleanup(ctx, activity.ActorClaude, in.Path, in.Number)
|
||||
if err != nil {
|
||||
return nil, forge.MergeResult{}, err
|
||||
}
|
||||
return nil, res, nil
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user