13 KiB
core/ide Modernisation — Implementation Plan
Date: 2026-03-14 Spec: 2026-03-14-ide-modernisation-design.md
Task 1 — Delete the 7 obsolete files
Remove all hand-rolled services that are now provided by ecosystem packages.
cd /Users/snider/Code/core/ide
rm mcp_bridge.go # Replaced by core/mcp Service + transports
rm webview_svc.go # Replaced by core/gui/pkg/display + webview
rm brain_mcp.go # Replaced by core/mcp/pkg/mcp/brain
rm claude_bridge.go # Replaced by core/mcp/pkg/mcp/ide Bridge
rm headless_mcp.go # Not needed — core/mcp runs in-process
rm headless.go # config gui.enabled flag; jobrunner belongs in core/agent
rm greetservice.go # Scaffold placeholder
Verification: ls *.go should show only main.go.
Task 2 — Rewrite main.go
Replace the current main.go with a thin Wails shell that wires core.Core with
ecosystem services. The full file contents:
package main
import (
"context"
"embed"
"io/fs"
"log"
"os"
"os/signal"
"runtime"
"syscall"
"forge.lthn.ai/core/config"
"forge.lthn.ai/core/go-ws"
"forge.lthn.ai/core/go/pkg/core"
guiMCP "forge.lthn.ai/core/gui/pkg/mcp"
"forge.lthn.ai/core/gui/pkg/display"
"forge.lthn.ai/core/ide/icons"
"forge.lthn.ai/core/mcp/pkg/mcp"
"forge.lthn.ai/core/mcp/pkg/mcp/brain"
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed all:frontend/dist/wails-angular-template/browser
var assets embed.FS
func main() {
// ── Flags ──────────────────────────────────────────────────
mcpOnly := false
for _, arg := range os.Args[1:] {
if arg == "--mcp" {
mcpOnly = true
}
}
// ── Configuration ──────────────────────────────────────────
cfg, _ := config.New()
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("failed to get working directory: %v", err)
}
// ── Shared resources (built before Core) ───────────────────
hub := ws.NewHub()
bridgeCfg := ide.DefaultConfig()
bridgeCfg.WorkspaceRoot = cwd
if url := os.Getenv("CORE_API_URL"); url != "" {
bridgeCfg.LaravelWSURL = url
}
if token := os.Getenv("CORE_API_TOKEN"); token != "" {
bridgeCfg.Token = token
}
bridge := ide.NewBridge(hub, bridgeCfg)
// ── Core framework ─────────────────────────────────────────
c, err := core.New(
core.WithName("ws", func(c *core.Core) (any, error) {
return hub, nil
}),
core.WithService(display.Register(nil)), // nil platform until Wails starts
core.WithName("mcp", func(c *core.Core) (any, error) {
return mcp.New(
mcp.WithWorkspaceRoot(cwd),
mcp.WithWSHub(hub),
mcp.WithSubsystem(brain.New(bridge)),
mcp.WithSubsystem(guiMCP.New(c)),
)
}),
)
if err != nil {
log.Fatalf("failed to create core: %v", err)
}
// Retrieve the MCP service for transport control
mcpSvc, err := core.ServiceFor[*mcp.Service](c, "mcp")
if err != nil {
log.Fatalf("failed to get MCP service: %v", err)
}
// ── Mode selection ─────────────────────────────────────────
if mcpOnly {
// stdio mode — Claude Code connects via --mcp flag
ctx, cancel := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer cancel()
// Start Core lifecycle manually
if err := c.ServiceStartup(ctx, nil); err != nil {
log.Fatalf("core startup failed: %v", err)
}
bridge.Start(ctx)
if err := mcpSvc.ServeStdio(ctx); err != nil {
log.Printf("MCP stdio error: %v", err)
}
_ = mcpSvc.Shutdown(ctx)
_ = c.ServiceShutdown(ctx)
return
}
if !guiEnabled(cfg) {
// No GUI — run Core with MCP transport in background
ctx, cancel := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer cancel()
if err := c.ServiceStartup(ctx, nil); err != nil {
log.Fatalf("core startup failed: %v", err)
}
bridge.Start(ctx)
go func() {
if err := mcpSvc.Run(ctx); err != nil {
log.Printf("MCP error: %v", err)
}
}()
<-ctx.Done()
shutdownCtx := context.Background()
_ = mcpSvc.Shutdown(shutdownCtx)
_ = c.ServiceShutdown(shutdownCtx)
return
}
// ── GUI mode ───────────────────────────────────────────────
staticAssets, err := fs.Sub(assets, "frontend/dist/wails-angular-template/browser")
if err != nil {
log.Fatal(err)
}
app := application.New(application.Options{
Name: "Core IDE",
Description: "Host UK Core IDE - Development Environment",
Services: []application.Service{
application.NewService(c),
},
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(staticAssets),
},
Mac: application.MacOptions{
ActivationPolicy: application.ActivationPolicyAccessory,
},
OnShutdown: func() {
ctx := context.Background()
_ = mcpSvc.Shutdown(ctx)
bridge.Shutdown()
},
})
// System tray
systray := app.SystemTray.New()
systray.SetTooltip("Core IDE")
if runtime.GOOS == "darwin" {
systray.SetTemplateIcon(icons.AppTray)
} else {
systray.SetDarkModeIcon(icons.AppTray)
systray.SetIcon(icons.AppTray)
}
// Tray panel window
trayWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "tray-panel",
Title: "Core IDE",
Width: 380,
Height: 480,
URL: "/tray",
Hidden: true,
Frameless: true,
BackgroundColour: application.NewRGB(26, 27, 38),
})
systray.AttachWindow(trayWindow).WindowOffset(5)
// Tray menu
trayMenu := app.Menu.New()
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
systray.SetMenu(trayMenu)
// Start MCP transport alongside Wails
go func() {
ctx := context.Background()
bridge.Start(ctx)
if err := mcpSvc.Run(ctx); err != nil {
log.Printf("MCP error: %v", err)
}
}()
log.Println("Starting Core IDE...")
if err := app.Run(); err != nil {
log.Fatal(err)
}
}
// guiEnabled checks whether the GUI should start.
// Returns false if config says gui.enabled: false, or if no display is available.
func guiEnabled(cfg *config.Config) bool {
if cfg != nil {
var guiCfg struct {
Enabled *bool `mapstructure:"enabled"`
}
if err := cfg.Get("gui", &guiCfg); err == nil && guiCfg.Enabled != nil {
return *guiCfg.Enabled
}
}
// Fall back to display detection
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
return true
}
return os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != ""
}
Key changes from the old main.go
- Three modes:
--mcp(stdio for Claude Code), no-GUI (headless with MCP_ADDR), GUI (Wails systray). core.Coreis the Wails service — itsServiceStartup/ServiceShutdowndrive all sub-service lifecycles.- Ecosystem wiring:
display.Register(nil)for GUI tools,brain.New(bridge)for OpenBrain,guiMCP.New(c)for 74 GUI MCP tools,mcp.WithWSHub(hub)for WS streaming. - No hand-rolled HTTP server —
mcp.Serviceowns the MCP protocol (stdio/TCP/Unix). The WS hub is injected via option. - IDE bridge uses
ide.DefaultConfig()fromcore/mcp/pkg/mcp/idewith env var overrides.
Task 3 — Update go.mod
3a. Replace go.mod with updated dependencies
module forge.lthn.ai/core/ide
go 1.26.0
require (
forge.lthn.ai/core/go v0.2.2
forge.lthn.ai/core/config v0.1.2
forge.lthn.ai/core/go-ws v0.1.3
forge.lthn.ai/core/gui v0.1.0
forge.lthn.ai/core/mcp v0.1.0
github.com/wailsapp/wails/v3 v3.0.0-alpha.74
)
Removed (no longer directly imported):
forge.lthn.ai/core/agent— brain tools now viacore/mcp/pkg/mcp/brainforge.lthn.ai/core/go-process— daemon/PID logic was in headless.goforge.lthn.ai/core/go-scm— Forgejo client was in headless.gogithub.com/gorilla/websocket— replaced bycore/mcp/pkg/mcp/ide(uses it internally)
Added:
forge.lthn.ai/core/gui— display service + 74 MCP toolsforge.lthn.ai/core/mcp— MCP server, brain subsystem, IDE bridge
3b. Sync workspace and tidy
cd /Users/snider/Code/core/ide
go mod tidy
cd /Users/snider/Code
go work sync
The go mod tidy will pull indirect dependencies and populate the require
block. The go work sync aligns versions across the workspace.
Task 4 — Update CLAUDE.md
Replace the existing CLAUDE.md with content reflecting the new architecture:
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Development Commands
\`\`\`bash
# Development (hot-reload GUI + Go rebuild)
wails3 dev
# Production build (preferred)
core build
# Frontend-only development
cd frontend && npm install && npm run dev
# Go tests
core go test # All tests
core go test --run TestName # Single test
core go cov # Coverage report
core go cov --open # Coverage in browser
# Quality assurance
core go qa # Format + vet + lint + test
core go qa full # + race detector, vuln scan, security audit
core go fmt # Format only
core go lint # Lint only
# Frontend tests
cd frontend && npm run test
\`\`\`
## Architecture
**Thin Wails shell** wiring ecosystem packages via `core.Core` dependency injection. Three operating modes:
### GUI Mode (default)
`main()` → Wails 3 application with embedded Angular frontend, system tray (macOS: accessory app, no Dock icon). Core framework manages all services:
- **display** (`core/gui`) — window management, webview automation, 74 MCP tools across 14 categories
- **MCP** (`core/mcp`) — Model Context Protocol server with file ops, brain subsystem, GUI subsystem
- **IDE bridge** (`core/mcp/pkg/mcp/ide`) — WebSocket bridge to Laravel core-agentic backend
- **WS hub** (`core/go-ws`) — WebSocket hub for Angular frontend communication
### MCP Mode (`--mcp`)
`core-ide --mcp` → stdio MCP server for Claude Code integration. No GUI, no HTTP. Configure in `.claude/.mcp.json`:
\`\`\`json
{
"mcpServers": {
"core-ide": {
"type": "stdio",
"command": "core-ide",
"args": ["--mcp"]
}
}
}
\`\`\`
### Headless Mode (no display or `gui.enabled: false`)
Core framework runs all services without Wails. MCP transport determined by `MCP_ADDR` env var (TCP if set, stdio otherwise).
### Frontend
Angular 20+ app embedded via `//go:embed`. Two routes: `/tray` (system tray panel, 380x480 frameless) and `/ide` (full IDE layout).
## Configuration
\`\`\`yaml
# .core/config.yaml
gui:
enabled: true # false = no Wails, Core still runs
mcp:
transport: stdio # stdio | tcp | unix
tcp:
port: 9877
brain:
api_url: http://localhost:8000
api_token: "" # or CORE_API_TOKEN env var
\`\`\`
## Environment Variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `CORE_API_URL` | `http://localhost:8000` | Laravel backend WebSocket URL |
| `CORE_API_TOKEN` | (empty) | Bearer token for Laravel backend auth |
| `MCP_ADDR` | (empty) | TCP address for MCP server (headless mode) |
## Workspace Dependencies
This module uses a Go workspace (`~/Code/go.work`) with `replace` directives for sibling modules:
- `../go` → `forge.lthn.ai/core/go`
- `../gui` → `forge.lthn.ai/core/gui`
- `../mcp` → `forge.lthn.ai/core/mcp`
- `../config` → `forge.lthn.ai/core/config`
- `../go-ws` → `forge.lthn.ai/core/go-ws`
## Conventions
- **UK English** in documentation and user-facing strings (colour, organisation, centre).
- **Conventional commits**: `type(scope): description` with co-author line `Co-Authored-By: Virgil <virgil@lethean.io>`.
- **Licence**: EUPL-1.2.
- All Go code is in `package main` (single-package application).
- Services are registered via `core.WithService` or `core.WithName` factory functions.
- MCP subsystems implement `mcp.Subsystem` interface from `core/mcp`.
Task 5 — Build verification
cd /Users/snider/Code/core/ide
go build ./...
If the build fails, the likely causes are:
-
Missing workspace entries — ensure
go.workincludes./core/mcpand./core/gui:cd /Users/snider/Code go work use ./core/mcp ./core/gui go work sync -
Stale go.sum — clear and regenerate:
cd /Users/snider/Code/core/ide rm go.sum go mod tidy -
Version mismatches — check that indirect dependency versions align:
go mod graph | grep -i conflict
Once go build ./... succeeds, run the quality check:
core go qa
This confirms formatting, vetting, linting, and tests all pass.