Add service layer and MCP server with read tools
Slice 1: internal/service is the one capability layer both the HTTP API and the MCP server call (AGENT.md 1.7); /api/repos and /api/repo route through it. Slice 2: internal/mcp serves an MCP server over Streamable HTTP at /mcp (go-sdk v1.8.0) with read tools list_repos and get_repo as thin adapters over the service, plus a round-trip test. Enabled air polling so hot reload works across the Docker-on-Windows bind mount. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// Package service is the ONE capability layer behind both the HTTP API and the
|
||||
// MCP server (AGENT.md §1.7). HTTP handlers and MCP tool handlers are thin
|
||||
// adapters that call these methods; Git/forge logic never lives in a handler.
|
||||
// Everything here goes through the internal/git (and later internal/forge)
|
||||
// boundaries and obeys the safety rules (§1.4).
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"gitmanager/internal/git"
|
||||
"gitmanager/internal/repos"
|
||||
)
|
||||
|
||||
// Service holds the shared dependencies the capabilities need.
|
||||
type Service struct {
|
||||
git *git.CLI
|
||||
index *repos.Index
|
||||
}
|
||||
|
||||
// New builds a Service over the git boundary and the scanner's repo index.
|
||||
func New(g *git.CLI, index *repos.Index) *Service {
|
||||
return &Service{git: g, index: index}
|
||||
}
|
||||
|
||||
// ListRepos returns a snapshot of every discovered repository.
|
||||
func (s *Service) ListRepos() []repos.State {
|
||||
return s.index.List()
|
||||
}
|
||||
|
||||
// GetRepo returns the cached state for one repo, or false if it is not indexed.
|
||||
// The path is cleaned so separator style does not defeat the exact-match lookup.
|
||||
func (s *Service) GetRepo(path string) (repos.State, bool) {
|
||||
return s.index.Get(filepath.Clean(path))
|
||||
}
|
||||
|
||||
// RepoDetail returns the enriched detail (branches, commits, remotes) for one
|
||||
// repo. The second return is false when the path is not an indexed repository —
|
||||
// we never run git against an arbitrary caller-supplied path (§1.3).
|
||||
func (s *Service) RepoDetail(ctx context.Context, path string) (repos.Detail, bool) {
|
||||
base, ok := s.index.Get(filepath.Clean(path))
|
||||
if !ok {
|
||||
return repos.Detail{}, false
|
||||
}
|
||||
return repos.BuildDetail(ctx, s.git, base), true
|
||||
}
|
||||
Reference in New Issue
Block a user