From c21a271dcf012ba5e9ed5d92182fa1d44efb1b84 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 30 Jan 2026 09:19:20 +0000 Subject: [PATCH] feat(framework): add service layer to git and agentic packages - Add pkg/framework/framework.go for cleaner imports - Add pkg/git/service.go with Core service wrapper - Add pkg/agentic/service.go with AI/Claude service wrapper - Services use IPC pattern with ACTION() dispatch Usage: import "github.com/host-uk/core/pkg/framework" app, _ := framework.New( framework.WithService(git.NewService(git.ServiceOptions{})), framework.WithServiceLock(), ) Co-Authored-By: Claude Opus 4.5 --- pkg/agentic/service.go | 107 +++++++++++++++++++++++++++++++++++++ pkg/framework/framework.go | 67 +++++++++++++++++++++++ pkg/git/service.go | 102 +++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 pkg/agentic/service.go create mode 100644 pkg/framework/framework.go create mode 100644 pkg/git/service.go diff --git a/pkg/agentic/service.go b/pkg/agentic/service.go new file mode 100644 index 0000000..f197200 --- /dev/null +++ b/pkg/agentic/service.go @@ -0,0 +1,107 @@ +package agentic + +import ( + "context" + "os" + "os/exec" + + "github.com/host-uk/core/pkg/framework" +) + +// Actions for AI service IPC + +// ActionCommit requests Claude to create a commit. +type ActionCommit struct { + Path string + Name string + CanEdit bool // allow Write/Edit tools +} + +// ActionPrompt sends a custom prompt to Claude. +type ActionPrompt struct { + Prompt string + WorkDir string + AllowedTools []string +} + +// ServiceOptions for configuring the AI service. +type ServiceOptions struct { + DefaultTools []string +} + +// DefaultServiceOptions returns sensible defaults. +func DefaultServiceOptions() ServiceOptions { + return ServiceOptions{ + DefaultTools: []string{"Bash", "Read", "Glob", "Grep"}, + } +} + +// Service provides AI/Claude operations as a Core service. +type Service struct { + *framework.ServiceRuntime[ServiceOptions] +} + +// NewService creates an AI service factory. +func NewService(opts ServiceOptions) func(*framework.Core) (any, error) { + return func(c *framework.Core) (any, error) { + return &Service{ + ServiceRuntime: framework.NewServiceRuntime(c, opts), + }, nil + } +} + +// OnStartup registers action handlers. +func (s *Service) OnStartup(ctx context.Context) error { + s.Core().RegisterAction(s.handle) + return nil +} + +func (s *Service) handle(c *framework.Core, msg framework.Message) error { + switch m := msg.(type) { + case ActionCommit: + return s.handleCommit(m) + case ActionPrompt: + return s.handlePrompt(m) + } + return nil +} + +func (s *Service) handleCommit(action ActionCommit) error { + prompt := Prompt("commit") + + tools := "Bash,Read,Glob,Grep" + if action.CanEdit { + tools = "Bash,Read,Write,Edit,Glob,Grep" + } + + cmd := exec.CommandContext(context.Background(), "claude", "-p", prompt, "--allowedTools", tools) + cmd.Dir = action.Path + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + return cmd.Run() +} + +func (s *Service) handlePrompt(action ActionPrompt) error { + tools := "Bash,Read,Glob,Grep" + if len(action.AllowedTools) > 0 { + tools = "" + for i, t := range action.AllowedTools { + if i > 0 { + tools += "," + } + tools += t + } + } + + cmd := exec.CommandContext(context.Background(), "claude", "-p", action.Prompt, "--allowedTools", tools) + if action.WorkDir != "" { + cmd.Dir = action.WorkDir + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + return cmd.Run() +} diff --git a/pkg/framework/framework.go b/pkg/framework/framework.go new file mode 100644 index 0000000..6cff7dd --- /dev/null +++ b/pkg/framework/framework.go @@ -0,0 +1,67 @@ +// Package framework provides the Core DI/service framework. +// Import this package for cleaner access to the framework types. +// +// Usage: +// +// import "github.com/host-uk/core/pkg/framework" +// +// app, _ := framework.New( +// framework.WithServiceLock(), +// ) +package framework + +import ( + core "github.com/host-uk/core/pkg/framework/core" +) + +// Re-export core types for cleaner imports +type ( + Core = core.Core + Option = core.Option + Message = core.Message + Startable = core.Startable + Stoppable = core.Stoppable + Config = core.Config + Display = core.Display + WindowOption = core.WindowOption + Features = core.Features + Contract = core.Contract + Error = core.Error + ServiceRuntime[T any] = core.ServiceRuntime[T] + Runtime = core.Runtime + ServiceFactory = core.ServiceFactory +) + +// Re-export core functions +var ( + New = core.New + WithService = core.WithService + WithName = core.WithName + WithApp = core.WithApp + WithAssets = core.WithAssets + WithServiceLock = core.WithServiceLock + App = core.App + E = core.E + NewRuntime = core.NewRuntime + NewWithFactories = core.NewWithFactories +) + +// NewServiceRuntime creates a new ServiceRuntime for a service. +func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] { + return core.NewServiceRuntime(c, opts) +} + +// Re-export generic functions +func ServiceFor[T any](c *Core, name string) (T, error) { + return core.ServiceFor[T](c, name) +} + +func MustServiceFor[T any](c *Core, name string) T { + return core.MustServiceFor[T](c, name) +} + +// Action types +type ( + ActionServiceStartup = core.ActionServiceStartup + ActionServiceShutdown = core.ActionServiceShutdown +) diff --git a/pkg/git/service.go b/pkg/git/service.go new file mode 100644 index 0000000..e26d7d4 --- /dev/null +++ b/pkg/git/service.go @@ -0,0 +1,102 @@ +package git + +import ( + "context" + + "github.com/host-uk/core/pkg/framework" +) + +// Actions for git service IPC + +// ActionStatus requests git status for paths. +type ActionStatus struct { + Paths []string + Names map[string]string +} + +// ActionPush requests git push for a path. +type ActionPush struct{ Path, Name string } + +// ActionPull requests git pull for a path. +type ActionPull struct{ Path, Name string } + +// ServiceOptions for configuring the git service. +type ServiceOptions struct { + WorkDir string +} + +// Service provides git operations as a Core service. +type Service struct { + *framework.ServiceRuntime[ServiceOptions] + lastStatus []RepoStatus +} + +// NewService creates a git service factory. +func NewService(opts ServiceOptions) func(*framework.Core) (any, error) { + return func(c *framework.Core) (any, error) { + return &Service{ + ServiceRuntime: framework.NewServiceRuntime(c, opts), + }, nil + } +} + +// OnStartup registers action handlers. +func (s *Service) OnStartup(ctx context.Context) error { + s.Core().RegisterAction(s.handle) + return nil +} + +func (s *Service) handle(c *framework.Core, msg framework.Message) error { + switch m := msg.(type) { + case ActionStatus: + return s.handleStatus(m) + case ActionPush: + return s.handlePush(m) + case ActionPull: + return s.handlePull(m) + } + return nil +} + +func (s *Service) handleStatus(action ActionStatus) error { + ctx := context.Background() + statuses := Status(ctx, StatusOptions{ + Paths: action.Paths, + Names: action.Names, + }) + s.lastStatus = statuses + return nil +} + +func (s *Service) handlePush(action ActionPush) error { + return Push(context.Background(), action.Path) +} + +func (s *Service) handlePull(action ActionPull) error { + return Pull(context.Background(), action.Path) +} + +// Status returns last status result. +func (s *Service) Status() []RepoStatus { return s.lastStatus } + +// DirtyRepos returns repos with uncommitted changes. +func (s *Service) DirtyRepos() []RepoStatus { + var dirty []RepoStatus + for _, st := range s.lastStatus { + if st.Error == nil && st.IsDirty() { + dirty = append(dirty, st) + } + } + return dirty +} + +// AheadRepos returns repos with unpushed commits. +func (s *Service) AheadRepos() []RepoStatus { + var ahead []RepoStatus + for _, st := range s.lastStatus { + if st.Error == nil && st.HasUnpushed() { + ahead = append(ahead, st) + } + } + return ahead +}