This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/tools/git.go
2026-03-19 18:50:27 +00:00

226 lines
6.0 KiB
Go

package tools
import (
"context"
"encoding/json"
"fmt"
"chat-switchboard/workspace"
)
// RegisterGitTools registers the 5 git-related workspace tools.
// Each declares RequireWorkspace + DenyVisitor availability via workspaceToolBase.
func RegisterGitTools(gitOps *workspace.GitOps) {
Register(&gitStatusTool{gitOps: gitOps})
Register(&gitDiffTool{gitOps: gitOps})
Register(&gitCommitTool{gitOps: gitOps})
Register(&gitLogTool{gitOps: gitOps})
Register(&gitBranchTool{gitOps: gitOps})
}
// ── git_status ───────────────────────────────
type gitStatusTool struct {
workspaceToolBase
gitOps *workspace.GitOps
}
func (t *gitStatusTool) Definition() ToolDef {
return ToolDef{
Name: "git_status",
DisplayName: "Git Status",
Description: "Show git working tree status: branch name, staged/modified/untracked files, ahead/behind remote counts.",
Category: "workspace",
Parameters: JSONSchema(map[string]interface{}{}, nil),
}
}
func (t *gitStatusTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
if err != nil {
return "", err
}
status, err := t.gitOps.Status(ctx, w)
if err != nil {
return "", err
}
data, _ := json.MarshalIndent(status, "", " ")
return string(data), nil
}
// ── git_diff ─────────────────────────────────
type gitDiffTool struct {
workspaceToolBase
gitOps *workspace.GitOps
}
func (t *gitDiffTool) Definition() ToolDef {
return ToolDef{
Name: "git_diff",
DisplayName: "Git Diff",
Description: "Show git diff for a specific file or all uncommitted changes.",
Category: "workspace",
Parameters: JSONSchema(map[string]interface{}{
"path": Prop("string", "File path to diff (empty for all changes)"),
}, nil),
}
}
func (t *gitDiffTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
var args struct {
Path string `json:"path"`
}
json.Unmarshal([]byte(argsJSON), &args)
w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
if err != nil {
return "", err
}
diff, err := t.gitOps.Diff(ctx, w, args.Path)
if err != nil {
return "", err
}
if diff == "" {
return "No changes.", nil
}
if len(diff) > 50000 {
diff = diff[:50000] + "\n... (truncated)"
}
return diff, nil
}
// ── git_commit ───────────────────────────────
type gitCommitTool struct {
workspaceToolBase
gitOps *workspace.GitOps
}
func (t *gitCommitTool) Definition() ToolDef {
return ToolDef{
Name: "git_commit",
DisplayName: "Git Commit",
Description: "Stage and commit files. Stages all changes if no paths specified.",
Category: "workspace",
Parameters: JSONSchema(map[string]interface{}{
"message": Prop("string", "Commit message"),
"paths": PropArray("Specific file paths to stage (empty = stage all)"),
}, []string{"message"}),
}
}
func (t *gitCommitTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
var args struct {
Message string `json:"message"`
Paths []string `json:"paths"`
}
if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
return "", fmt.Errorf("invalid arguments: %w", err)
}
if args.Message == "" {
return "", fmt.Errorf("commit message is required")
}
w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
if err != nil {
return "", err
}
if err := t.gitOps.Commit(ctx, w, args.Message, args.Paths); err != nil {
return "", err
}
return fmt.Sprintf("Committed: %s", args.Message), nil
}
// ── git_log ──────────────────────────────────
type gitLogTool struct {
workspaceToolBase
gitOps *workspace.GitOps
}
func (t *gitLogTool) Definition() ToolDef {
return ToolDef{
Name: "git_log",
DisplayName: "Git Log",
Description: "Show recent commit history.",
Category: "workspace",
Parameters: JSONSchema(map[string]interface{}{
"count": Prop("integer", "Number of commits to show (default 20, max 100)"),
}, nil),
}
}
func (t *gitLogTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
var args struct {
Count int `json:"count"`
}
json.Unmarshal([]byte(argsJSON), &args)
if args.Count <= 0 {
args.Count = 20
}
if args.Count > 100 {
args.Count = 100
}
w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
if err != nil {
return "", err
}
entries, err := t.gitOps.Log(ctx, w, args.Count)
if err != nil {
return "", err
}
data, _ := json.MarshalIndent(entries, "", " ")
return string(data), nil
}
// ── git_branch ───────────────────────────────
type gitBranchTool struct {
workspaceToolBase
gitOps *workspace.GitOps
}
func (t *gitBranchTool) Definition() ToolDef {
return ToolDef{
Name: "git_branch",
DisplayName: "Git Branch",
Description: "List branches or switch to a different branch.",
Category: "workspace",
Parameters: JSONSchema(map[string]interface{}{
"checkout": Prop("string", "Branch name to switch to (empty = list only)"),
}, nil),
}
}
func (t *gitBranchTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
var args struct {
Checkout string `json:"checkout"`
}
json.Unmarshal([]byte(argsJSON), &args)
w, err := t.gitOps.LoadWorkspace(ctx, execCtx.WorkspaceID)
if err != nil {
return "", err
}
if args.Checkout != "" {
if err := t.gitOps.Checkout(ctx, w, args.Checkout); err != nil {
return "", err
}
return fmt.Sprintf("Switched to branch: %s", args.Checkout), nil
}
branches, current, err := t.gitOps.BranchList(ctx, w)
if err != nil {
return "", err
}
result := map[string]interface{}{
"current": current,
"branches": branches,
}
data, _ := json.MarshalIndent(result, "", " ")
return string(data), nil
}