fix: resolve API signature mismatches after IO migration merge

Reconcile callers with actual function signatures after merging IO
migration branches. Some functions gained io.Medium params (repos.*),
others kept their original signatures (release.*, cache.*, container.*).

- Add io.Local to repos.LoadRegistry/FindRegistry/ScanDirectory callers
- Remove extra io.Local from release.ConfigExists/LoadConfig/WriteConfig callers
- Fix cache.New call (remove nil Medium arg)
- Add missing IsCPPProject to build discovery
- Add missing fields to mcp.Service struct (subsystems, logger, etc.)
- Add DefaultTCPAddr constant to mcp transport
- Fix node.go interface check (coreio.Medium, not coreio.Node)
- Fix container.linuxkit LoadState/EnsureLogsDir arg counts
- Fix vm templates to use package-level functions
- Remove unused Medium field from DaemonOptions

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-08 21:55:10 +00:00
parent a852ab7ff8
commit 7d134f9d0c
18 changed files with 65 additions and 52 deletions

View file

@ -5,7 +5,6 @@ import (
"github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/i18n"
"github.com/host-uk/core/pkg/io"
"github.com/host-uk/core/pkg/release" "github.com/host-uk/core/pkg/release"
) )
@ -18,14 +17,14 @@ func runCIReleaseInit() error {
cli.Print("%s %s\n\n", releaseDimStyle.Render(i18n.Label("init")), i18n.T("cmd.ci.init.initializing")) cli.Print("%s %s\n\n", releaseDimStyle.Render(i18n.Label("init")), i18n.T("cmd.ci.init.initializing"))
// Check if already initialized // Check if already initialized
if release.ConfigExists(io.Local, cwd) { if release.ConfigExists(cwd) {
cli.Text(i18n.T("cmd.ci.init.already_initialized")) cli.Text(i18n.T("cmd.ci.init.already_initialized"))
return nil return nil
} }
// Create release config // Create release config
cfg := release.DefaultConfig() cfg := release.DefaultConfig()
if err := release.WriteConfig(io.Local, cfg, cwd); err != nil { if err := release.WriteConfig(cfg, cwd); err != nil {
return cli.Err("%s: %w", i18n.T("i18n.fail.create", "config"), err) return cli.Err("%s: %w", i18n.T("i18n.fail.create", "config"), err)
} }

View file

@ -7,7 +7,6 @@ import (
"github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/i18n"
"github.com/host-uk/core/pkg/io"
"github.com/host-uk/core/pkg/release" "github.com/host-uk/core/pkg/release"
) )
@ -23,7 +22,7 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
} }
// Load configuration // Load configuration
cfg, err := release.LoadConfig(io.Local, projectDir) cfg, err := release.LoadConfig(projectDir)
if err != nil { if err != nil {
return cli.WrapVerb(err, "load", "config") return cli.WrapVerb(err, "load", "config")
} }

View file

@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/io"
"github.com/host-uk/core/pkg/log" "github.com/host-uk/core/pkg/log"
"github.com/host-uk/core/pkg/mcp" "github.com/host-uk/core/pkg/mcp"
) )
@ -118,7 +117,6 @@ func runDaemon(cfg Config) error {
// Create daemon with health checks // Create daemon with health checks
daemon := cli.NewDaemon(cli.DaemonOptions{ daemon := cli.NewDaemon(cli.DaemonOptions{
Medium: io.Local,
PIDFile: cfg.PIDFile, PIDFile: cfg.PIDFile,
HealthAddr: cfg.HealthAddr, HealthAddr: cfg.HealthAddr,
ShutdownTimeout: 30, ShutdownTimeout: 30,

View file

@ -15,7 +15,7 @@ import (
"strings" "strings"
"github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/errors" errors "github.com/host-uk/core/pkg/framework/core"
"github.com/host-uk/core/pkg/git" "github.com/host-uk/core/pkg/git"
"github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/i18n"
"github.com/host-uk/core/pkg/io" "github.com/host-uk/core/pkg/io"
@ -225,12 +225,12 @@ func runApply() error {
// getApplyTargetRepos gets repos to apply command to // getApplyTargetRepos gets repos to apply command to
func getApplyTargetRepos() ([]*repos.Repo, error) { func getApplyTargetRepos() ([]*repos.Repo, error) {
// Load registry // Load registry
registryPath, err := repos.FindRegistry() registryPath, err := repos.FindRegistry(io.Local)
if err != nil { if err != nil {
return nil, errors.E("dev.apply", "failed to find registry", err) return nil, errors.E("dev.apply", "failed to find registry", err)
} }
registry, err := repos.LoadRegistry(registryPath) registry, err := repos.LoadRegistry(io.Local, registryPath)
if err != nil { if err != nil {
return nil, errors.E("dev.apply", "failed to load registry", err) return nil, errors.E("dev.apply", "failed to load registry", err)
} }

View file

@ -207,12 +207,12 @@ func runFileSync(source string) error {
// resolveTargetRepos resolves the --to pattern to actual repos // resolveTargetRepos resolves the --to pattern to actual repos
func resolveTargetRepos(pattern string) ([]*repos.Repo, error) { func resolveTargetRepos(pattern string) ([]*repos.Repo, error) {
// Load registry // Load registry
registryPath, err := repos.FindRegistry() registryPath, err := repos.FindRegistry(coreio.Local)
if err != nil { if err != nil {
return nil, log.E("dev.sync", "failed to find registry", err) return nil, log.E("dev.sync", "failed to find registry", err)
} }
registry, err := repos.LoadRegistry(registryPath) registry, err := repos.LoadRegistry(coreio.Local, registryPath)
if err != nil { if err != nil {
return nil, log.E("dev.sync", "failed to load registry", err) return nil, log.E("dev.sync", "failed to load registry", err)
} }

View file

@ -30,22 +30,22 @@ func loadRegistry(registryPath string) (*repos.Registry, string, error) {
var registryDir string var registryDir string
if registryPath != "" { if registryPath != "" {
reg, err = repos.LoadRegistry(registryPath) reg, err = repos.LoadRegistry(io.Local, registryPath)
if err != nil { if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry")) return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
} }
registryDir = filepath.Dir(registryPath) registryDir = filepath.Dir(registryPath)
} else { } else {
registryPath, err = repos.FindRegistry() registryPath, err = repos.FindRegistry(io.Local)
if err == nil { if err == nil {
reg, err = repos.LoadRegistry(registryPath) reg, err = repos.LoadRegistry(io.Local, registryPath)
if err != nil { if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry")) return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
} }
registryDir = filepath.Dir(registryPath) registryDir = filepath.Dir(registryPath)
} else { } else {
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
reg, err = repos.ScanDirectory(cwd) reg, err = repos.ScanDirectory(io.Local, cwd)
if err != nil { if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.scan", "directory")) return nil, "", cli.Wrap(err, i18n.T("i18n.fail.scan", "directory"))
} }

View file

@ -74,7 +74,7 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error
cacheDir = filepath.Join(filepath.Dir(regPath), ".core", "cache") cacheDir = filepath.Join(filepath.Dir(regPath), ".core", "cache")
} }
c, err := cache.New(nil, cacheDir, 0) c, err := cache.New(cacheDir, 0)
if err != nil { if err != nil {
c = nil c = nil
} }

View file

@ -22,7 +22,7 @@ import (
// runRegistrySetup loads a registry from path and runs setup. // runRegistrySetup loads a registry from path and runs setup.
func runRegistrySetup(ctx context.Context, registryPath, only string, dryRun, all, runBuild bool) error { func runRegistrySetup(ctx context.Context, registryPath, only string, dryRun, all, runBuild bool) error {
reg, err := repos.LoadRegistry(registryPath) reg, err := repos.LoadRegistry(coreio.Local, registryPath)
if err != nil { if err != nil {
return fmt.Errorf("failed to load registry: %w", err) return fmt.Errorf("failed to load registry: %w", err)
} }

View file

@ -16,8 +16,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var templateManager = container.NewTemplateManager(io.Local)
// addVMTemplatesCommand adds the 'templates' command under vm. // addVMTemplatesCommand adds the 'templates' command under vm.
func addVMTemplatesCommand(parent *cobra.Command) { func addVMTemplatesCommand(parent *cobra.Command) {
templatesCmd := &cobra.Command{ templatesCmd := &cobra.Command{
@ -71,7 +69,7 @@ func addTemplatesVarsCommand(parent *cobra.Command) {
} }
func listTemplates() error { func listTemplates() error {
templates := templateManager.ListTemplates() templates := container.ListTemplates()
if len(templates) == 0 { if len(templates) == 0 {
fmt.Println(i18n.T("cmd.vm.templates.no_templates")) fmt.Println(i18n.T("cmd.vm.templates.no_templates"))
@ -102,7 +100,7 @@ func listTemplates() error {
} }
func showTemplate(name string) error { func showTemplate(name string) error {
content, err := templateManager.GetTemplate(name) content, err := container.GetTemplate(name)
if err != nil { if err != nil {
return err return err
} }
@ -114,7 +112,7 @@ func showTemplate(name string) error {
} }
func showTemplateVars(name string) error { func showTemplateVars(name string) error {
content, err := templateManager.GetTemplate(name) content, err := container.GetTemplate(name)
if err != nil { if err != nil {
return err return err
} }
@ -151,7 +149,7 @@ func showTemplateVars(name string) error {
// RunFromTemplate builds and runs a LinuxKit image from a template. // RunFromTemplate builds and runs a LinuxKit image from a template.
func RunFromTemplate(templateName string, vars map[string]string, runOpts container.RunOptions) error { func RunFromTemplate(templateName string, vars map[string]string, runOpts container.RunOptions) error {
// Apply template with variables // Apply template with variables
content, err := templateManager.ApplyTemplate(templateName, vars) content, err := container.ApplyTemplate(templateName, vars)
if err != nil { if err != nil {
return fmt.Errorf(i18n.T("common.error.failed", map[string]any{"Action": "apply template"})+": %w", err) return fmt.Errorf(i18n.T("common.error.failed", map[string]any{"Action": "apply template"})+": %w", err)
} }

View file

@ -5,7 +5,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/host-uk/core/pkg/errors" errors "github.com/host-uk/core/pkg/framework/core"
"github.com/host-uk/core/pkg/io" "github.com/host-uk/core/pkg/io"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View file

@ -9,7 +9,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/host-uk/core/pkg/errors" errors "github.com/host-uk/core/pkg/framework/core"
"github.com/host-uk/core/pkg/io" "github.com/host-uk/core/pkg/io"
) )

View file

@ -9,7 +9,6 @@ import (
"github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/framework/core" "github.com/host-uk/core/pkg/framework/core"
"github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/i18n"
"github.com/host-uk/core/pkg/io"
"github.com/host-uk/core/pkg/release" "github.com/host-uk/core/pkg/release"
) )
@ -51,7 +50,7 @@ func runRelease(ctx context.Context, dryRun bool, version string, draft, prerele
} }
// Check for release config // Check for release config
if !release.ConfigExists(io.Local, projectDir) { if !release.ConfigExists(projectDir) {
cli.Print("%s %s\n", cli.Print("%s %s\n",
buildErrorStyle.Render(i18n.Label("error")), buildErrorStyle.Render(i18n.Label("error")),
i18n.T("cmd.build.release.error.no_config"), i18n.T("cmd.build.release.error.no_config"),
@ -61,7 +60,7 @@ func runRelease(ctx context.Context, dryRun bool, version string, draft, prerele
} }
// Load configuration // Load configuration
cfg, err := release.LoadConfig(io.Local, projectDir) cfg, err := release.LoadConfig(projectDir)
if err != nil { if err != nil {
return core.E("release", "load config", err) return core.E("release", "load config", err)
} }

View file

@ -83,6 +83,11 @@ func IsPHPProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, markerComposer)) return fileExists(fs, filepath.Join(dir, markerComposer))
} }
// IsCPPProject checks if the directory contains a C++ project (CMakeLists.txt).
func IsCPPProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, "CMakeLists.txt"))
}
// fileExists checks if a file exists and is not a directory. // fileExists checks if a file exists and is not a directory.
func fileExists(fs io.Medium, path string) bool { func fileExists(fs io.Medium, path string) bool {
return fs.IsFile(path) return fs.IsFile(path)

View file

@ -27,7 +27,7 @@ func NewLinuxKitManager(m io.Medium) (*LinuxKitManager, error) {
return nil, fmt.Errorf("failed to determine state path: %w", err) return nil, fmt.Errorf("failed to determine state path: %w", err)
} }
state, err := LoadState(m, statePath) state, err := LoadState(statePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load state: %w", err) return nil, fmt.Errorf("failed to load state: %w", err)
} }
@ -90,7 +90,7 @@ func (m *LinuxKitManager) Run(ctx context.Context, image string, opts RunOptions
} }
// Ensure logs directory exists // Ensure logs directory exists
if err := EnsureLogsDir(m.medium); err != nil { if err := EnsureLogsDir(); err != nil {
return nil, fmt.Errorf("failed to create logs directory: %w", err) return nil, fmt.Errorf("failed to create logs directory: %w", err)
} }

View file

@ -25,7 +25,7 @@ type Node struct {
} }
// compile-time interface check // compile-time interface check
var _ coreio.Node = (*Node)(nil) var _ coreio.Medium = (*Node)(nil)
// New creates a new, empty Node. // New creates a new, empty Node.
func New() *Node { func New() *Node {

View file

@ -5,11 +5,15 @@ package mcp
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/host-uk/core/pkg/io" "github.com/host-uk/core/pkg/io"
"github.com/host-uk/core/pkg/log"
"github.com/host-uk/core/pkg/process"
"github.com/host-uk/core/pkg/ws"
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
) )
@ -19,6 +23,12 @@ type Service struct {
server *mcp.Server server *mcp.Server
workspaceRoot string // Root directory for file operations (empty = unrestricted) workspaceRoot string // Root directory for file operations (empty = unrestricted)
medium io.Medium // Filesystem medium for sandboxed operations medium io.Medium // Filesystem medium for sandboxed operations
subsystems []Subsystem // Additional subsystems registered via WithSubsystem
logger *log.Logger // Logger for tool execution auditing
processService *process.Service // Process management service (optional)
wsHub *ws.Hub // WebSocket hub for real-time streaming (optional)
wsServer *http.Server // WebSocket HTTP server (optional)
wsAddr string // WebSocket server address
} }
// Option configures a Service. // Option configures a Service.
@ -61,7 +71,10 @@ func New(opts ...Option) (*Service, error) {
} }
server := mcp.NewServer(impl, nil) server := mcp.NewServer(impl, nil)
s := &Service{server: server} s := &Service{
server: server,
logger: log.Default(),
}
// Default to current working directory with sandboxed medium // Default to current working directory with sandboxed medium
cwd, err := os.Getwd() cwd, err := os.Getwd()

View file

@ -12,6 +12,9 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
) )
// DefaultTCPAddr is the default address for the MCP TCP server.
const DefaultTCPAddr = "127.0.0.1:9100"
// maxMCPMessageSize is the maximum size for MCP JSON-RPC messages (10 MB). // maxMCPMessageSize is the maximum size for MCP JSON-RPC messages (10 MB).
const maxMCPMessageSize = 10 * 1024 * 1024 const maxMCPMessageSize = 10 * 1024 * 1024

View file

@ -5,7 +5,6 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/host-uk/core/pkg/io"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -54,7 +53,7 @@ changelog:
` `
dir := setupConfigTestDir(t, content) dir := setupConfigTestDir(t, content)
cfg, err := LoadConfig(io.Local, dir) cfg, err := LoadConfig(dir)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, cfg) require.NotNil(t, cfg)
@ -77,7 +76,7 @@ changelog:
t.Run("returns defaults when config file missing", func(t *testing.T) { t.Run("returns defaults when config file missing", func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
cfg, err := LoadConfig(io.Local, dir) cfg, err := LoadConfig(dir)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, cfg) require.NotNil(t, cfg)
@ -97,7 +96,7 @@ project:
` `
dir := setupConfigTestDir(t, content) dir := setupConfigTestDir(t, content)
cfg, err := LoadConfig(io.Local, dir) cfg, err := LoadConfig(dir)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, cfg) require.NotNil(t, cfg)
@ -114,7 +113,7 @@ project:
t.Run("sets project directory on load", func(t *testing.T) { t.Run("sets project directory on load", func(t *testing.T) {
dir := setupConfigTestDir(t, "version: 1") dir := setupConfigTestDir(t, "version: 1")
cfg, err := LoadConfig(io.Local, dir) cfg, err := LoadConfig(dir)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, dir, cfg.projectDir) assert.Equal(t, dir, cfg.projectDir)
}) })
@ -129,7 +128,7 @@ project:
` `
dir := setupConfigTestDir(t, content) dir := setupConfigTestDir(t, content)
cfg, err := LoadConfig(io.Local, dir) cfg, err := LoadConfig(dir)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, cfg) assert.Nil(t, cfg)
assert.Contains(t, err.Error(), "failed to parse config file") assert.Contains(t, err.Error(), "failed to parse config file")
@ -146,7 +145,7 @@ project:
err = os.Mkdir(configPath, 0755) err = os.Mkdir(configPath, 0755)
require.NoError(t, err) require.NoError(t, err)
cfg, err := LoadConfig(io.Local, dir) cfg, err := LoadConfig(dir)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, cfg) assert.Nil(t, cfg)
assert.Contains(t, err.Error(), "failed to read config file") assert.Contains(t, err.Error(), "failed to read config file")
@ -205,17 +204,17 @@ func TestConfigPath_Good(t *testing.T) {
func TestConfigExists_Good(t *testing.T) { func TestConfigExists_Good(t *testing.T) {
t.Run("returns true when config exists", func(t *testing.T) { t.Run("returns true when config exists", func(t *testing.T) {
dir := setupConfigTestDir(t, "version: 1") dir := setupConfigTestDir(t, "version: 1")
assert.True(t, ConfigExists(io.Local, dir)) assert.True(t, ConfigExists(dir))
}) })
t.Run("returns false when config missing", func(t *testing.T) { t.Run("returns false when config missing", func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
assert.False(t, ConfigExists(io.Local, dir)) assert.False(t, ConfigExists(dir))
}) })
t.Run("returns false when .core dir missing", func(t *testing.T) { t.Run("returns false when .core dir missing", func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
assert.False(t, ConfigExists(io.Local, dir)) assert.False(t, ConfigExists(dir))
}) })
} }
@ -227,14 +226,14 @@ func TestWriteConfig_Good(t *testing.T) {
cfg.Project.Name = "testapp" cfg.Project.Name = "testapp"
cfg.Project.Repository = "owner/testapp" cfg.Project.Repository = "owner/testapp"
err := WriteConfig(io.Local, cfg, dir) err := WriteConfig(cfg, dir)
require.NoError(t, err) require.NoError(t, err)
// Verify file exists // Verify file exists
assert.True(t, ConfigExists(io.Local, dir)) assert.True(t, ConfigExists(dir))
// Reload and verify // Reload and verify
loaded, err := LoadConfig(io.Local, dir) loaded, err := LoadConfig(dir)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "testapp", loaded.Project.Name) assert.Equal(t, "testapp", loaded.Project.Name)
assert.Equal(t, "owner/testapp", loaded.Project.Repository) assert.Equal(t, "owner/testapp", loaded.Project.Repository)
@ -244,7 +243,7 @@ func TestWriteConfig_Good(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
cfg := DefaultConfig() cfg := DefaultConfig()
err := WriteConfig(io.Local, cfg, dir) err := WriteConfig(cfg, dir)
require.NoError(t, err) require.NoError(t, err)
// Check directory was created // Check directory was created
@ -321,7 +320,7 @@ func TestWriteConfig_Bad(t *testing.T) {
defer func() { _ = os.Chmod(coreDir, 0755) }() defer func() { _ = os.Chmod(coreDir, 0755) }()
cfg := DefaultConfig() cfg := DefaultConfig()
err = WriteConfig(io.Local, cfg, dir) err = WriteConfig(cfg, dir)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to write config file") assert.Contains(t, err.Error(), "failed to write config file")
}) })
@ -332,7 +331,7 @@ func TestWriteConfig_Bad(t *testing.T) {
} }
// Use a path that doesn't exist and can't be created // Use a path that doesn't exist and can't be created
cfg := DefaultConfig() cfg := DefaultConfig()
err := WriteConfig(io.Local, cfg, "/nonexistent/path/that/cannot/be/created") err := WriteConfig(cfg, "/nonexistent/path/that/cannot/be/created")
assert.Error(t, err) assert.Error(t, err)
}) })
} }