diff --git a/.codex/config.toml b/.codex/config.toml index 7f410034..08c75d8d 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -38,7 +38,7 @@ sandbox_mode = "workspace-write" [mcp_servers.core-agent] command = "core-agent" args = ["mcp"] -required = true +required = false startup_timeout_sec = 15 tool_timeout_sec = 120 diff --git a/.codex/config.toml.bak b/.codex/config.toml.bak new file mode 100644 index 00000000..7f410034 --- /dev/null +++ b/.codex/config.toml.bak @@ -0,0 +1,64 @@ +# Core Agent — Codex Configuration +# Shared between CLI and IDE extension + +model = "gpt-5.4" +model_reasoning_effort = "xhigh" +approval_policy = "on-request" +sandbox_mode = "workspace-write" +personality = "pragmatic" + +# Default to LEM when available +# oss_provider = "ollama" + +[profiles.review] +model = "gpt-5.4" +model_reasoning_effort = "xhigh" +approval_policy = "never" +sandbox_mode = "read-only" + +[profiles.quick] +model = "gpt-5.4" +model_reasoning_effort = "low" +approval_policy = "never" + +[profiles.implement] +model = "gpt-5.4" +model_reasoning_effort = "high" +approval_policy = "never" +sandbox_mode = "workspace-write" + +[profiles.lem] +model = "lem-4b" +model_provider = "ollama" +model_reasoning_effort = "high" +approval_policy = "never" +sandbox_mode = "workspace-write" + +# Core Agent MCP Server +[mcp_servers.core-agent] +command = "core-agent" +args = ["mcp"] +required = true +startup_timeout_sec = 15 +tool_timeout_sec = 120 + +[mcp_servers.core-agent.env] +FORGE_TOKEN = "${FORGE_TOKEN}" +CORE_BRAIN_KEY = "${CORE_BRAIN_KEY}" +MONITOR_INTERVAL = "15s" + +# Model providers: codex CLI 0.122+ ships built-in `ollama` and `lmstudio` +# providers pointing at the same default localhost ports, so project-level +# overrides are both redundant and rejected ("reserved built-in provider IDs"). + +# Agent configuration +[agents] +max_threads = 4 +max_depth = 1 +job_max_runtime_seconds = 600 + +# Features +[features] +multi_agent = true +shell_snapshot = true +undo = true diff --git a/.core/reference/command.go b/.core/reference/command.go index 660f866c..6e1412db 100644 --- a/.core/reference/command.go +++ b/.core/reference/command.go @@ -20,7 +20,6 @@ // "deploy/to/homelab" → "cmd.deploy.to.homelab.description" package core - // CommandAction is the function signature for command handlers. // // func(opts core.Options) core.Result @@ -28,14 +27,17 @@ type CommandAction func(Options) Result // Command is the DTO for an executable operation. // Commands are declarative — they carry enough information for multiple consumers: +// // - core.Cli() runs the Action +// // - core/cli adds rich help, completion, man pages +// // - go-process wraps Managed commands with lifecycle (PID, health, signals) // -// c.Command("serve", core.Command{ -// Action: handler, -// Managed: "process.daemon", // go-process provides start/stop/restart -// }) +// c.Command("serve", core.Command{ +// Action: handler, +// Managed: "process.daemon", // go-process provides start/stop/restart +// }) type Command struct { Name string Description string // i18n key — derived from path if empty diff --git a/.core/reference/contract.go b/.core/reference/contract.go index 8718a90e..3be32401 100644 --- a/.core/reference/contract.go +++ b/.core/reference/contract.go @@ -85,17 +85,17 @@ type CoreOption func(*Core) Result // c.Run() func New(opts ...CoreOption) *Core { c := &Core{ - app: &App{}, - data: &Data{Registry: NewRegistry[*Embed]()}, - drive: &Drive{Registry: NewRegistry[*DriveHandle]()}, - fs: (&Fs{}).New("/"), - config: (&Config{}).New(), - error: &ErrorPanic{}, - log: &ErrorLog{}, - lock: &Lock{locks: NewRegistry[*sync.RWMutex]()}, - ipc: &Ipc{actions: NewRegistry[*Action](), tasks: NewRegistry[*Task]()}, - info: systemInfo, - i18n: &I18n{}, + app: &App{}, + data: &Data{Registry: NewRegistry[*Embed]()}, + drive: &Drive{Registry: NewRegistry[*DriveHandle]()}, + fs: (&Fs{}).New("/"), + config: (&Config{}).New(), + error: &ErrorPanic{}, + log: &ErrorLog{}, + lock: &Lock{locks: NewRegistry[*sync.RWMutex]()}, + ipc: &Ipc{actions: NewRegistry[*Action](), tasks: NewRegistry[*Task]()}, + info: systemInfo, + i18n: &I18n{}, api: &API{protocols: NewRegistry[StreamFactory]()}, services: &ServiceRegistry{Registry: NewRegistry[*Service]()}, commands: &CommandRegistry{Registry: NewRegistry[*Command]()}, diff --git a/.core/reference/embed.go b/.core/reference/embed.go index fd81d283..77190eaf 100644 --- a/.core/reference/embed.go +++ b/.core/reference/embed.go @@ -224,7 +224,7 @@ func GeneratePack(pkg ScannedPackage) Result { return Result{b.String(), true} } - b.WriteString("import \"dappco.re/go/core\"\n\n") + b.WriteString("import \"dappco.re/go\"\n\n") b.WriteString("func init() {\n") // Pack groups (entire directories) @@ -287,12 +287,18 @@ func compress(input string) (string, error) { return "", err } if _, err := gz.Write([]byte(input)); err != nil { - _ = gz.Close() - _ = b64.Close() + if closeErr := gz.Close(); closeErr != nil { + return "", err + } + if closeErr := b64.Close(); closeErr != nil { + return "", err + } return "", err } if err := gz.Close(); err != nil { - _ = b64.Close() + if closeErr := b64.Close(); closeErr != nil { + return "", err + } return "", err } if err := b64.Close(); err != nil { diff --git a/.core/reference/i18n.go b/.core/reference/i18n.go index 7061ce85..27b11de3 100644 --- a/.core/reference/i18n.go +++ b/.core/reference/i18n.go @@ -72,7 +72,9 @@ func (i *I18n) SetTranslator(t Translator) { locale := i.locale i.mu.Unlock() if t != nil && locale != "" { - _ = t.SetLanguage(locale) + if result := t.SetLanguage(locale); !result.OK { + return + } } } diff --git a/.core/reference/ipc.go b/.core/reference/ipc.go index bedbd65b..c5357e7b 100644 --- a/.core/reference/ipc.go +++ b/.core/reference/ipc.go @@ -110,4 +110,3 @@ func (c *Core) RegisterActions(handlers ...func(*Core, Message) Result) { c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handlers...) c.ipc.ipcMu.Unlock() } - diff --git a/.deps/core-compat/ax7_triplets_test.go b/.deps/core-compat/ax7_triplets_test.go new file mode 100644 index 00000000..20136913 --- /dev/null +++ b/.deps/core-compat/ax7_triplets_test.go @@ -0,0 +1,43 @@ +package core + +import coretest "dappco.re/go" + +func TestAX7_NewRegistry_Good(t *coretest.T) { + reg := NewRegistry[string]() + coretest.AssertNotNil(t, reg) + coretest.AssertEqual(t, 0, reg.Len()) +} + +func TestAX7_NewRegistry_Bad(t *coretest.T) { + reg := NewRegistry[int]() + got := reg.Get("missing") + coretest.AssertFalse(t, got.OK) + coretest.AssertNil(t, got.Value) +} + +func TestAX7_NewRegistry_Ugly(t *coretest.T) { + reg := NewRegistry[*int]() + reg.Set("", nil) + got := reg.Get("") + coretest.AssertTrue(t, got.OK) + coretest.AssertNil(t, got.Value) +} + +func TestAX7_NewServiceRuntime_Good(t *coretest.T) { + runtime := NewServiceRuntime(New(), "opts") + coretest.AssertNotNil(t, runtime) + coretest.AssertNotNil(t, runtime.Core) +} + +func TestAX7_NewServiceRuntime_Bad(t *coretest.T) { + runtime := NewServiceRuntime[string](nil, "") + coretest.AssertNotNil(t, runtime) + coretest.AssertNil(t, runtime.Core) +} + +func TestAX7_NewServiceRuntime_Ugly(t *coretest.T) { + opts := map[string]string{"name": "api"} + runtime := NewServiceRuntime(New(), opts) + coretest.AssertEqual(t, "api", runtime.Options["name"]) + coretest.AssertNotNil(t, runtime.Core) +} diff --git a/.deps/core-compat/core.go b/.deps/core-compat/core.go new file mode 100644 index 00000000..d37cc36e --- /dev/null +++ b/.deps/core-compat/core.go @@ -0,0 +1,103 @@ +package core + +import newcore "dappco.re/go" + +type Action = newcore.Action +type ActionHandler = newcore.ActionHandler +type App = newcore.App +type AtomicPointer[T any] = newcore.AtomicPointer[T] +type Cli = newcore.Cli +type Command = newcore.Command +type CommandAction = newcore.CommandAction +type Core = newcore.Core +type CoreOption = newcore.CoreOption +type Embed = newcore.Embed +type Fs = newcore.Fs +type Lock = newcore.Lock +type Log = newcore.Log +type Message = newcore.Message +type Mutex = newcore.Mutex +type Once = newcore.Once +type Option = newcore.Option +type Options = newcore.Options +type Process = newcore.Process +type Query = newcore.Query +type Registry[T any] = newcore.Registry[T] +type Result = newcore.Result +type RWMutex = newcore.RWMutex +type Service = newcore.Service +type ServiceRuntime[T any] = newcore.ServiceRuntime[T] +type Startable = newcore.Startable +type Stoppable = newcore.Stoppable +type Translator = newcore.Translator + +var As = newcore.As +var Atoi = newcore.Atoi +var CleanPath = newcore.CleanPath +var Concat = newcore.Concat +var Contains = newcore.Contains +var E = newcore.E +var Env = newcore.Env +var ErrorJoin = newcore.ErrorJoin +var Exit = newcore.Exit +var FormatInt = newcore.FormatInt +var HasPrefix = newcore.HasPrefix +var HasSuffix = newcore.HasSuffix +var ID = newcore.ID +var Itoa = newcore.Itoa +var Is = newcore.Is +var IsDigit = newcore.IsDigit +var IsLetter = newcore.IsLetter +var IsSpace = newcore.IsSpace +var JSONMarshal = newcore.JSONMarshal +var JSONMarshalString = newcore.JSONMarshalString +var JSONUnmarshal = newcore.JSONUnmarshal +var JSONUnmarshalString = newcore.JSONUnmarshalString +var Join = newcore.Join +var JoinPath = newcore.JoinPath +var Lower = newcore.Lower +var New = newcore.New +var NewBuffer = newcore.NewBuffer +var NewBuilder = newcore.NewBuilder +var NewError = newcore.NewError +var NewOptions = newcore.NewOptions +var NewReader = newcore.NewReader +var Operation = newcore.Operation +var Path = newcore.Path +var PathBase = newcore.PathBase +var PathDir = newcore.PathDir +var PathExt = newcore.PathExt +var PathGlob = newcore.PathGlob +var PathIsAbs = newcore.PathIsAbs +var PathJoin = newcore.PathJoin +var Print = newcore.Print +var Println = newcore.Println +var ReadAll = newcore.ReadAll +var Replace = newcore.Replace +var SHA256 = newcore.SHA256 +var Security = newcore.Security +var Split = newcore.Split +var SplitN = newcore.SplitN +var Sprint = newcore.Sprint +var Sprintf = newcore.Sprintf +var ParseInt = newcore.ParseInt +var Trim = newcore.Trim +var TrimPrefix = newcore.TrimPrefix +var TrimSuffix = newcore.TrimSuffix +var Upper = newcore.Upper +var ValidateName = newcore.ValidateName +var URLParse = newcore.URLParse +var URLEncode = newcore.URLEncode +var Warn = newcore.Warn +var WithName = newcore.WithName +var WithOption = newcore.WithOption +var WithService = newcore.WithService +var Wrap = newcore.Wrap + +func NewRegistry[T any]() *Registry[T] { + return newcore.NewRegistry[T]() +} + +func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] { + return newcore.NewServiceRuntime(c, opts) +} diff --git a/.deps/core-compat/go.mod b/.deps/core-compat/go.mod new file mode 100644 index 00000000..8b51a97b --- /dev/null +++ b/.deps/core-compat/go.mod @@ -0,0 +1,5 @@ +module dappco.re/go/core + +go 1.26.0 + +require dappco.re/go v0.9.0 diff --git a/cmd/core-agent/commands.go b/cmd/core-agent/commands.go index 4d105d6b..2443cc05 100644 --- a/cmd/core-agent/commands.go +++ b/cmd/core-agent/commands.go @@ -5,8 +5,8 @@ package main import ( "os" + "dappco.re/go" "dappco.re/go/agent/pkg/agentic" - "dappco.re/go/core" ) type applicationCommandSet struct { diff --git a/cmd/core-agent/commands_example_test.go b/cmd/core-agent/commands_example_test.go index 58085763..38ead494 100644 --- a/cmd/core-agent/commands_example_test.go +++ b/cmd/core-agent/commands_example_test.go @@ -3,7 +3,7 @@ package main import ( - "dappco.re/go/core" + "dappco.re/go" ) func Example_registerApplicationCommands() { diff --git a/cmd/core-agent/commands_test.go b/cmd/core-agent/commands_test.go index d1b6bf1f..2d7968e4 100644 --- a/cmd/core-agent/commands_test.go +++ b/cmd/core-agent/commands_test.go @@ -8,10 +8,9 @@ import ( "os" "testing" + "dappco.re/go" agentpkg "dappco.re/go/agent" "dappco.re/go/agent/pkg/agentic" - "dappco.re/go/core" - "github.com/stretchr/testify/assert" ) // newTestCore creates a minimal Core with application commands registered. @@ -66,21 +65,21 @@ func TestCommands_ApplyLogLevel_Good(t *testing.T) { defer core.SetLevel(core.LevelInfo) args := applyLogLevel([]string{"--quiet", "version"}) - assert.Equal(t, []string{"version"}, args) + core.AssertEqual(t, []string{"version"}, args) } func TestCommands_ApplyLogLevel_Bad(t *testing.T) { defer core.SetLevel(core.LevelInfo) args := applyLogLevel([]string{"status"}) - assert.Equal(t, []string{"status"}, args) + core.AssertEqual(t, []string{"status"}, args) } func TestCommands_ApplyLogLevel_Ugly(t *testing.T) { defer core.SetLevel(core.LevelInfo) args := applyLogLevel([]string{"version", "-q"}) - assert.Equal(t, []string{"version"}, args) + core.AssertEqual(t, []string{"version"}, args) } func TestCommands_StartupArgs_Good(t *testing.T) { @@ -88,7 +87,7 @@ func TestCommands_StartupArgs_Good(t *testing.T) { withArgs(t, "core-agent", "--debug", "check") args := startupArgs() - assert.Equal(t, []string{"check"}, args) + core.AssertEqual(t, []string{"check"}, args) } func TestCommands_StartupArgs_Bad(t *testing.T) { @@ -96,7 +95,7 @@ func TestCommands_StartupArgs_Bad(t *testing.T) { withArgs(t, "core-agent", "status") args := startupArgs() - assert.Equal(t, []string{"status"}, args) + core.AssertEqual(t, []string{"status"}, args) } func TestCommands_StartupArgs_Ugly(t *testing.T) { @@ -104,15 +103,15 @@ func TestCommands_StartupArgs_Ugly(t *testing.T) { withArgs(t, "core-agent", "version", "-q") args := startupArgs() - assert.Equal(t, []string{"version"}, args) + core.AssertEqual(t, []string{"version"}, args) } func TestCommands_RegisterApplicationCommands_Good(t *testing.T) { c := newTestCore(t) cmds := c.Commands() - assert.Contains(t, cmds, "version") - assert.Contains(t, cmds, "check") - assert.Contains(t, cmds, "env") + core.AssertContains(t, cmds, "version") + core.AssertContains(t, cmds, "check") + core.AssertContains(t, cmds, "env") } func TestCommands_Version_Good(t *testing.T) { @@ -123,7 +122,7 @@ func TestCommands_Version_Good(t *testing.T) { }) r := c.Cli().Run("version") - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_VersionDev_Bad(t *testing.T) { @@ -132,14 +131,14 @@ func TestCommands_VersionDev_Bad(t *testing.T) { c.App().Version = "dev" r := c.Cli().Run("version") - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_Check_Good(t *testing.T) { c := newTestCore(t) r := c.Cli().Run("check") - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_Check_Good_BranchWorkspaceCount(t *testing.T) { @@ -149,9 +148,9 @@ func TestCommands_Check_Good_BranchWorkspaceCount(t *testing.T) { wsRoot := core.JoinPath(root, "workspace") ws := core.JoinPath(wsRoot, "core", "go-io", "feature", "new-ui") - assert.True(t, agentic.LocalFs().EnsureDir(agentic.WorkspaceRepoDir(ws)).OK) - assert.True(t, agentic.LocalFs().EnsureDir(agentic.WorkspaceMetaDir(ws)).OK) - assert.True(t, agentic.LocalFs().Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(agentic.WorkspaceStatus{ + core.AssertTrue(t, agentic.LocalFs().EnsureDir(agentic.WorkspaceRepoDir(ws)).OK) + core.AssertTrue(t, agentic.LocalFs().EnsureDir(agentic.WorkspaceMetaDir(ws)).OK) + core.AssertTrue(t, agentic.LocalFs().Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(agentic.WorkspaceStatus{ Status: "running", Repo: "go-io", Agent: "codex", @@ -159,23 +158,23 @@ func TestCommands_Check_Good_BranchWorkspaceCount(t *testing.T) { output := captureStdout(t, func() { r := c.Cli().Run("check") - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "1 workspaces") + core.AssertContains(t, output, "1 workspaces") } func TestCommands_Env_Good(t *testing.T) { c := newTestCore(t) r := c.Cli().Run("env") - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CliUnknown_Bad(t *testing.T) { c := newTestCore(t) r := c.Cli().Run("nonexistent") - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CliBanner_Good(t *testing.T) { diff --git a/cmd/core-agent/main.go b/cmd/core-agent/main.go index d34207c8..46e8d1b3 100644 --- a/cmd/core-agent/main.go +++ b/cmd/core-agent/main.go @@ -5,8 +5,8 @@ package main import ( "context" + "dappco.re/go" agentpkg "dappco.re/go/agent" - "dappco.re/go/core" "dappco.re/go/agent/pkg/agentic" "dappco.re/go/agent/pkg/brain" diff --git a/cmd/core-agent/main_example_test.go b/cmd/core-agent/main_example_test.go index 8439c927..9523d1c7 100644 --- a/cmd/core-agent/main_example_test.go +++ b/cmd/core-agent/main_example_test.go @@ -3,8 +3,8 @@ package main import ( + core "dappco.re/go" agentpkg "dappco.re/go/agent" - core "dappco.re/go/core" ) func Example_newCoreAgent() { diff --git a/cmd/core-agent/main_test.go b/cmd/core-agent/main_test.go index d6c4f82f..7d07ffd9 100644 --- a/cmd/core-agent/main_test.go +++ b/cmd/core-agent/main_test.go @@ -5,15 +5,14 @@ package main import ( "testing" + "dappco.re/go" agentpkg "dappco.re/go/agent" "dappco.re/go/agent/pkg/agentic" "dappco.re/go/agent/pkg/brain" "dappco.re/go/agent/pkg/monitor" "dappco.re/go/agent/pkg/runner" "dappco.re/go/agent/pkg/setup" - "dappco.re/go/core" "dappco.re/go/mcp/pkg/mcp" - "github.com/stretchr/testify/assert" ) func withVersion(t *testing.T, value string) { @@ -28,38 +27,38 @@ func TestMain_NewCoreAgent_Good(t *testing.T) { c := newCoreAgent() - assert.Equal(t, "core-agent", c.App().Name) - assert.Equal(t, "0.15.0", c.App().Version) - assert.Contains(t, c.Services(), "process") - assert.Contains(t, c.Services(), "agentic") - assert.Contains(t, c.Services(), "runner") - assert.Contains(t, c.Services(), "monitor") - assert.Contains(t, c.Services(), "brain") - assert.Contains(t, c.Services(), "setup") - assert.Contains(t, c.Services(), "mcp") - assert.Contains(t, c.Commands(), "version") - assert.Contains(t, c.Commands(), "check") - assert.Contains(t, c.Commands(), "env") - assert.Contains(t, c.Actions(), "process.run") + core.AssertEqual(t, "core-agent", c.App().Name) + core.AssertEqual(t, "0.15.0", c.App().Version) + core.AssertContains(t, c.Services(), "process") + core.AssertContains(t, c.Services(), "agentic") + core.AssertContains(t, c.Services(), "runner") + core.AssertContains(t, c.Services(), "monitor") + core.AssertContains(t, c.Services(), "brain") + core.AssertContains(t, c.Services(), "setup") + core.AssertContains(t, c.Services(), "mcp") + core.AssertContains(t, c.Commands(), "version") + core.AssertContains(t, c.Commands(), "check") + core.AssertContains(t, c.Commands(), "env") + core.AssertContains(t, c.Actions(), "process.run") service := c.Service("agentic") - assert.True(t, service.OK) - assert.IsType(t, &agentic.PrepSubsystem{}, service.Value) + core.AssertTrue(t, service.OK) + assertIsType(t, &agentic.PrepSubsystem{}, service.Value) service = c.Service("runner") - assert.True(t, service.OK) - assert.IsType(t, &runner.Service{}, service.Value) + core.AssertTrue(t, service.OK) + assertIsType(t, &runner.Service{}, service.Value) service = c.Service("monitor") - assert.True(t, service.OK) - assert.IsType(t, &monitor.Subsystem{}, service.Value) + core.AssertTrue(t, service.OK) + assertIsType(t, &monitor.Subsystem{}, service.Value) service = c.Service("brain") - assert.True(t, service.OK) - assert.IsType(t, &brain.DirectSubsystem{}, service.Value) + core.AssertTrue(t, service.OK) + assertIsType(t, &brain.DirectSubsystem{}, service.Value) service = c.Service("setup") - assert.True(t, service.OK) - assert.IsType(t, &setup.Service{}, service.Value) + core.AssertTrue(t, service.OK) + assertIsType(t, &setup.Service{}, service.Value) service = c.Service("mcp") - assert.True(t, service.OK) - assert.IsType(t, &mcp.Service{}, service.Value) + core.AssertTrue(t, service.OK) + assertIsType(t, &mcp.Service{}, service.Value) } func TestMain_NewCoreAgentBanner_Good(t *testing.T) { @@ -67,22 +66,27 @@ func TestMain_NewCoreAgentBanner_Good(t *testing.T) { c := newCoreAgent() - assert.Equal(t, "core-agent 0.15.0 — agentic orchestration for the Core ecosystem", c.Cli().Banner()) + core.AssertEqual(t, "core-agent 0.15.0 — agentic orchestration for the Core ecosystem", c.Cli().Banner()) } func TestMain_RunApp_Good(t *testing.T) { withVersion(t, "0.15.0") - assert.NoError(t, runApp(newTestCore(t), []string{"version"})) + err := runApp(newTestCore(t), []string{"version"}) + core.AssertNoError(t, err) + core.AssertNil(t, err) } func TestMain_RunApp_Bad(t *testing.T) { - assert.EqualError(t, runApp(nil, []string{"version"}), "main.runApp: core is required") + err := runApp(nil, []string{"version"}) + core.AssertError(t, err, "main.runApp: core is required") + core.AssertContains(t, err.Error(), "core is required") } func TestMain_ResultError_Ugly(t *testing.T) { err := resultError("main.runApp", "cli failed", core.Result{}) - assert.EqualError(t, err, "main.runApp: cli failed") + core.AssertError(t, err, "main.runApp: cli failed") + core.AssertContains(t, err.Error(), "cli failed") } func TestMain_NewCoreAgentFallback_Ugly(t *testing.T) { @@ -90,6 +94,6 @@ func TestMain_NewCoreAgentFallback_Ugly(t *testing.T) { c := newCoreAgent() - assert.Equal(t, "dev", c.App().Version) - assert.Equal(t, "core-agent dev — agentic orchestration for the Core ecosystem", c.Cli().Banner()) + core.AssertEqual(t, "dev", c.App().Version) + core.AssertEqual(t, "core-agent dev — agentic orchestration for the Core ecosystem", c.Cli().Banner()) } diff --git a/cmd/core-agent/mcp_service_example_test.go b/cmd/core-agent/mcp_service_example_test.go index 0daccf89..54464aca 100644 --- a/cmd/core-agent/mcp_service_example_test.go +++ b/cmd/core-agent/mcp_service_example_test.go @@ -3,7 +3,7 @@ package main import ( - "dappco.re/go/core" + "dappco.re/go" "dappco.re/go/mcp/pkg/mcp" ) diff --git a/cmd/core-agent/mcp_service_test.go b/cmd/core-agent/mcp_service_test.go index b643a5e3..c1ca9968 100644 --- a/cmd/core-agent/mcp_service_test.go +++ b/cmd/core-agent/mcp_service_test.go @@ -5,13 +5,11 @@ package main import ( "testing" + "dappco.re/go" "dappco.re/go/agent/pkg/agentic" "dappco.re/go/agent/pkg/brain" "dappco.re/go/agent/pkg/monitor" - "dappco.re/go/core" "dappco.re/go/mcp/pkg/mcp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMCP_Register_Good(t *testing.T) { @@ -22,9 +20,9 @@ func TestMCP_Register_Good(t *testing.T) { result := c.Service("mcp") - require.True(t, result.OK) + core.RequireTrue(t, result.OK) _, ok := result.Value.(*mcp.Service) - assert.True(t, ok) + core.AssertTrue(t, ok) } func TestMCP_Register_Bad(t *testing.T) { @@ -32,7 +30,7 @@ func TestMCP_Register_Bad(t *testing.T) { result := c.Service("mcp") - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestMCP_Register_Ugly(t *testing.T) { @@ -47,7 +45,7 @@ func TestMCP_Register_Ugly(t *testing.T) { result := c.Service("mcp") - require.True(t, result.OK) + core.RequireTrue(t, result.OK) service := result.Value.(*mcp.Service) - assert.Len(t, service.Subsystems(), 3) + core.AssertLen(t, service.Subsystems(), 3) } diff --git a/cmd/core-agent/test_assertions_test.go b/cmd/core-agent/test_assertions_test.go new file mode 100644 index 00000000..ae78848a --- /dev/null +++ b/cmd/core-agent/test_assertions_test.go @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package main + +import ( + "reflect" + "testing" + + core "dappco.re/go" +) + +func assertIsType(t *testing.T, want, got any, msg ...string) { + t.Helper() + core.AssertEqual(t, reflect.TypeOf(want), reflect.TypeOf(got), msg...) +} diff --git a/cmd/core-agent/update.go b/cmd/core-agent/update.go index 328e03f9..6d4aa4f3 100644 --- a/cmd/core-agent/update.go +++ b/cmd/core-agent/update.go @@ -3,8 +3,8 @@ package main import ( + core "dappco.re/go" agentpkg "dappco.re/go/agent" - core "dappco.re/go/core" ) // agentpkg.Version = "0.15.0" diff --git a/cmd/core-agent/update_example_test.go b/cmd/core-agent/update_example_test.go index 8062a1b1..1305d569 100644 --- a/cmd/core-agent/update_example_test.go +++ b/cmd/core-agent/update_example_test.go @@ -3,8 +3,8 @@ package main import ( + core "dappco.re/go" agentpkg "dappco.re/go/agent" - core "dappco.re/go/core" ) func Example_updateChannel() { diff --git a/cmd/core-agent/update_test.go b/cmd/core-agent/update_test.go index 3d07608c..89858ca4 100644 --- a/cmd/core-agent/update_test.go +++ b/cmd/core-agent/update_test.go @@ -5,8 +5,8 @@ package main import ( "testing" + core "dappco.re/go" agentpkg "dappco.re/go/agent" - "github.com/stretchr/testify/assert" ) func TestUpdate_UpdateChannel_Good(t *testing.T) { @@ -14,7 +14,7 @@ func TestUpdate_UpdateChannel_Good(t *testing.T) { t.Cleanup(func() { agentpkg.Version = "" }) - assert.Equal(t, "stable", updateChannel()) + core.AssertEqual(t, "stable", updateChannel()) } func TestUpdate_UpdateChannelDev_Good(t *testing.T) { @@ -22,12 +22,14 @@ func TestUpdate_UpdateChannelDev_Good(t *testing.T) { t.Cleanup(func() { agentpkg.Version = "" }) - assert.Equal(t, "dev", updateChannel()) + core.AssertEqual(t, "dev", updateChannel()) } func TestUpdate_UpdateChannelEmpty_Bad(t *testing.T) { agentpkg.Version = "" - assert.Equal(t, "dev", updateChannel()) + got := updateChannel() + core.AssertEqual(t, "dev", got) + core.AssertNotEmpty(t, got) } func TestUpdate_UpdateChannelPrerelease_Ugly(t *testing.T) { @@ -35,7 +37,7 @@ func TestUpdate_UpdateChannelPrerelease_Ugly(t *testing.T) { t.Cleanup(func() { agentpkg.Version = "" }) - assert.Equal(t, "prerelease", updateChannel()) + core.AssertEqual(t, "prerelease", updateChannel()) } func TestUpdate_UpdateChannelNumericSuffix_Ugly(t *testing.T) { @@ -43,7 +45,7 @@ func TestUpdate_UpdateChannelNumericSuffix_Ugly(t *testing.T) { t.Cleanup(func() { agentpkg.Version = "" }) - assert.Equal(t, "prerelease", updateChannel()) + core.AssertEqual(t, "prerelease", updateChannel()) } func TestUpdate_ApplicationVersion_Good(t *testing.T) { @@ -51,10 +53,12 @@ func TestUpdate_ApplicationVersion_Good(t *testing.T) { t.Cleanup(func() { agentpkg.Version = "" }) - assert.Equal(t, "1.2.3", applicationVersion()) + core.AssertEqual(t, "1.2.3", applicationVersion()) } func TestUpdate_ApplicationVersion_Bad(t *testing.T) { agentpkg.Version = "" - assert.Equal(t, "dev", applicationVersion()) + got := applicationVersion() + core.AssertEqual(t, "dev", got) + core.AssertNotEmpty(t, got) } diff --git a/go.mod b/go.mod index 042c7980..b033dd84 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,49 @@ module dappco.re/go/agent -go 1.26.0 +go 1.26.2 require ( - dappco.re/go/api v0.8.0-alpha.1 - dappco.re/go/core v0.8.0-alpha.1 + dappco.re/go v0.9.0 + dappco.re/go/core/api v0.0.0-00010101000000-000000000000 dappco.re/go/forge v0.8.0-alpha.1 dappco.re/go/mcp v0.8.0-alpha.1 - dappco.re/go/process v0.8.0-alpha.1 + dappco.re/go/process v0.8.0-alpha.1.0.20260427150750-5bd42af95f46 dappco.re/go/store v0.8.0-alpha.1 - dappco.re/go/ws v0.8.0-alpha.1 + dappco.re/go/ws v0.8.0-alpha.1.0.20260427142937-36f01754d2e9 forge.lthn.ai/Snider/Poindexter v0.0.0-20260223032814-5ab751f16d06 github.com/gin-gonic/gin v1.12.0 github.com/gorilla/websocket v1.5.3 github.com/modelcontextprotocol/go-sdk v1.5.0 - github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - dappco.re/go/ai v0.8.0-alpha.1 // indirect + dappco.re/go/api v0.8.0-alpha.1.0.20260427143506-f95002059d53 // indirect + dappco.re/go/core v0.8.0-alpha.1 // indirect + dappco.re/go/core/io v0.4.2 // indirect + dappco.re/go/core/log v0.0.4 // indirect + dappco.re/go/inference v0.8.0-alpha.1 // indirect + github.com/apache/arrow-go/v18 v18.1.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/google/flatbuffers v25.1.24+incompatible // indirect + github.com/influxdata/influxdb-client-go/v2 v2.14.0 // indirect + github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect + github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728 // indirect + github.com/marcboeker/go-duckdb v1.8.5 // indirect + github.com/oapi-codegen/runtime v1.0.0 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect +) + +require ( + dappco.re/go/ai v0.8.0-alpha.1.0.20260425225549-d43f4dbd25b8 // indirect dappco.re/go/io v0.8.0-alpha.1 // indirect dappco.re/go/log v0.8.0-alpha.1 // indirect - dappco.re/go/rag v0.8.0-alpha.1 // indirect - dappco.re/go/webview v0.8.0-alpha.1 // indirect + dappco.re/go/rag v0.8.0-alpha.1.0.20260427161922-2a59096f2aca // indirect + dappco.re/go/webview v0.8.0-alpha.1.0.20260425135446-1c47ae2c183c // indirect github.com/99designs/gqlgen v0.17.88 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/Snider/Poindexter v0.0.0-20260104200422-91146b212a1f @@ -40,7 +60,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect @@ -93,7 +112,6 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/ollama/ollama v0.18.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/qdrant/go-client v1.17.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect @@ -133,10 +151,68 @@ require ( google.golang.org/protobuf v1.36.11 // indirect modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect -modernc.org/memory v1.11.0 // indirect -modernc.org/sqlite v1.47.0 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.47.0 // indirect ) replace dappco.re/go/mcp => ../mcp replace forge.lthn.ai/Snider/Poindexter => ../../snider/Poindexter + +replace dappco.re/go => ../go + +replace dappco.re/go/store => ../go-store + +replace dappco.re/go/io => ../go-io + +replace dappco.re/go/log => ../go-log + +replace dappco.re/go/cli => ../cli + +replace dappco.re/go/inference => ../go-inference + +replace dappco.re/go/scm => ../go-scm + +replace dappco.re/go/proxy => ../go-proxy + +replace dappco.re/go/i18n => ../go-i18n + +replace dappco.re/go/core/api => ../core/api + +replace dappco.re/go/core/cli => ../core/cli + +replace dappco.re/go/core/inference => ../core/go-inference + +replace dappco.re/go/core/io => ../core/go-io + +replace dappco.re/go/core/process => ../core/go-process + +replace dappco.re/go/core/store => ../core/go-store + +replace dappco.re/go/core/ws => ../core/go-ws + +replace dappco.re/go/core/webview => ../core/go-webview + +replace dappco.re/go/core/ai => ../core/go-ai + +replace dappco.re/go/core/miner => ../go-miner + +replace dappco.re/go/core/log => ../core/api/go-log + +replace dappco.re/go/core/config => ../go-scm/core/config + +replace dappco.re/go/ai => ../mcp/internal/shims/go-ai + +replace dappco.re/go/rag => ../mcp/internal/shims/go-rag + +replace dappco.re/go/webview => ../mcp/internal/shims/go-webview + +replace dappco.re/go/api => ../mcp/internal/shims/go-api + +replace dappco.re/go/core => ./.deps/core-compat + +replace dappco.re/go/forge => ../go-forge + +replace dappco.re/go/process => ../go-process + +replace dappco.re/go/ws => ../go-ws diff --git a/go.sum b/go.sum index 3c9d75fb..d141db47 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,10 @@ -dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk= -dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= -dappco.re/go/core/ai v0.2.2 h1:fkSKm3ezAljYbghlax5qHDm11uq7LUyIedIQO1PtdcY= -dappco.re/go/core/ai v0.2.2/go.mod h1:+MZN/EArn/W2ag91McL034WxdMSO4IPqFcQER5/POGU= -dappco.re/go/core/api v0.3.0 h1:uWYgDQ+B4e5pXPX3S5lMsqSJamfpui3LWD5hcdwvWew= -dappco.re/go/core/api v0.3.0/go.mod h1:1ZDNwPHV6YjkUsjtC3nfLk6U4eqWlQ6qj6yT/MB8r6k= -dappco.re/go/core/forge v0.3.1 h1:44fFkNiv/YdI96vqzuaMe5x9kAuYI03WgOtNvRDLAEc= -dappco.re/go/core/forge v0.3.1/go.mod h1:WK4hDGt2q2ignUEwasda3oKiLloiNRQJyedsKPSejZ0= -dappco.re/go/core/io v0.4.1 h1:15dm7ldhFIAuZOrBiQG6XVZDpSvCxtZsUXApwTAB3wQ= -dappco.re/go/core/io v0.4.1/go.mod h1:w71dukyunczLb8frT9JOd5B78PjwWQD3YAXiCt3AcPA= -dappco.re/go/core/log v0.1.2 h1:pQSZxKD8VycdvjNJmatXbPSq2OxcP2xHbF20zgFIiZI= -dappco.re/go/core/log v0.1.2/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= -dappco.re/go/core/process v0.5.1 h1:USnVQRzbfGolgju4/L/gyAU7anzgHzr//z8vmR9ppug= -dappco.re/go/core/process v0.5.1/go.mod h1:Zh8H+Rw6LCmjFmO0X6zxy9Z6O4EUKXrE6+XiPDuWuuA= -dappco.re/go/core/rag v0.1.13 h1:R2Q+Xw5YenT4uFemXLBu+xQYtyUIYGSmMln5/Z+nol4= -dappco.re/go/core/rag v0.1.13/go.mod h1:wthXtCqYEChjlGIHcJXetlgk49lPDmzG6jFWd1PEIZc= -dappco.re/go/core/store v0.3.0 h1:DECJB0A8dovqtX7w0/nGCV1XZLGI1/1pUt4SMM6GHh0= -dappco.re/go/core/store v0.3.0/go.mod h1:mirctw1g2ZfZRrALz43bomurXJFSQwd+rZdfIwPVqF8= -dappco.re/go/core/webview v0.2.1 h1:rdy2sV+MS6RZsav8BiARJxtWhfx7eOAJp3b1Ynp1sYs= -dappco.re/go/core/webview v0.2.1/go.mod h1:Qdo1V/sJJwOnL0hYd3+vzVUJxWYC8eGyILZROya6KoM= -dappco.re/go/core/ws v0.4.0 h1:yEDV9whXyo+GWzBSjuB3NiLiH2bmBPBWD6rydwHyBn8= -dappco.re/go/core/ws v0.4.0/go.mod h1:L1rrgW6zU+DztcVBJW2yO5Lm3rGXpyUMOA8OL9zsAok= github.com/99designs/gqlgen v0.17.88 h1:neMQDgehMwT1vYIOx/w5ZYPUU/iMNAJzRO44I5Intoc= github.com/99designs/gqlgen v0.17.88/go.mod h1:qeqYFEgOeSKqWedOjogPizimp2iu4E23bdPvl4jTYic= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Snider/Poindexter v0.0.0-20260104200422-91146b212a1f h1:+EnE414H9wUaBeUVNjyErusrxSbBGnGV6MBhTw/em0k= github.com/Snider/Poindexter v0.0.0-20260104200422-91146b212a1f/go.mod h1:nhgkbg4zWA4AS2Ga3RmcvdsyiI9TdxvSqe5EVBSb3Hk= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= @@ -36,10 +15,39 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= +github.com/apache/arrow-go/v18 v18.1.0 h1:agLwJUiVuwXZdwPYVrlITfx7bndULJ/dggbnLFgDp/Y= +github.com/apache/arrow-go/v18 v18.1.0/go.mod h1:tigU/sIgKNXaesf5d7Y95jBBKS5KsxTqYBKXFsvKzo0= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= +github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21 h1:SwGMTMLIlvDNyhMteQ6r8IJSBPlRdXX5d4idhIGbkXA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12 h1:qtJZ70afD3ISKWnoX3xB0J2otEqu3LqicRcDBqsj0hQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8zjTHSXFhB9L/2OY8Dqs0xXiLjF30jA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 h1:csi9NLpFZXb9fxY7rS1xVzgPRGMt7MSNWeQ6eo247kE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= @@ -160,6 +168,10 @@ github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v25.1.24+incompatible h1:4wPqL3K7GzBd1CwyhSd3usxLKOaJN/AC6puCca6Jm7o= +github.com/google/flatbuffers v25.1.24+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -181,22 +193,39 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4= +github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728 h1:QwWKgMY28TAXaDl+ExRDqGQltzXqN/xypdKP86niVn8= +github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/marcboeker/go-duckdb v1.8.5 h1:tkYp+TANippy0DaIOP5OEfBEwbUINqiFqgwMQ44jME0= +github.com/marcboeker/go-duckdb v1.8.5/go.mod h1:6mK7+WQE4P4u5AFLvVBmhFxY5fvhymFptghgJX6B+/8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/modelcontextprotocol/go-sdk v1.5.0 h1:CHU0FIX9kpueNkxuYtfYQn1Z0slhFzBZuq+x6IiblIU= github.com/modelcontextprotocol/go-sdk v1.5.0/go.mod h1:gggDIhoemhWs3BGkGwd1umzEXCEMMvAnhTrnbXJKKKA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -206,10 +235,16 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= +github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= github.com/ollama/ollama v0.18.2 h1:RsOY8oZ6TufRiPgsSlKJp4/V/X+oBREscUlEHZfd554= github.com/ollama/ollama v0.18.2/go.mod h1:tCX4IMV8DHjl3zY0THxuEkpWDZSOchJpzTuLACpMwFw= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= +github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -233,6 +268,7 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -263,6 +299,8 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= @@ -299,6 +337,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= @@ -324,10 +364,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGKFgUKuo+7GnR3FX5L7CbveeZc= +golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -341,6 +384,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= diff --git a/php/Agentic/Livewire/BrainExplorer.php b/php/Agentic/Livewire/BrainExplorer.php index 78c070b2..f50f105c 100644 --- a/php/Agentic/Livewire/BrainExplorer.php +++ b/php/Agentic/Livewire/BrainExplorer.php @@ -16,6 +16,12 @@ #[Title('Brain Explorer')] #[Layout('hub::admin.layouts.app')] +/** + * Browse, search, and forget workspace brain memories from the admin hub. + * + * @example + * Livewire::test(BrainExplorer::class, ['workspaceId' => 7])->call('searchMemories'); + */ class BrainExplorer extends HubComponent { public int $workspaceId = 0; @@ -37,6 +43,12 @@ class BrainExplorer extends HubComponent public bool $usedFallbackSearch = false; + /** + * Initialise the explorer with access checks and recent memories. + * + * @example + * $component->mount(7); + */ public function mount(?int $workspaceId = null): void { $this->checkHadesAccess(); @@ -45,12 +57,24 @@ public function mount(?int $workspaceId = null): void } #[Computed] + /** + * Return the supported memory types for the filter dropdown. + * + * @example + * $types = $component->memoryTypes(); + */ public function memoryTypes(): array { return BrainMemory::VALID_TYPES; } #[Computed] + /** + * Return agent identifiers that currently have memories in the workspace. + * + * @example + * $agents = $component->availableAgents(); + */ public function availableAgents(): array { if ($this->workspaceId <= 0) { @@ -73,6 +97,12 @@ public function availableAgents(): array } } + /** + * Search the brain index or fall back to a direct database search. + * + * @example + * Livewire::test(BrainExplorer::class, ['workspaceId' => 7])->set('query', 'dispatch')->call('searchMemories'); + */ public function searchMemories(): void { $this->validate([ @@ -109,6 +139,12 @@ public function searchMemories(): void } } + /** + * Remove one memory from the workspace brain and refresh the result list. + * + * @example + * Livewire::test(BrainExplorer::class, ['workspaceId' => 7])->call('forgetMemory', 'mem_123'); + */ public function forgetMemory(string $memoryId): void { ForgetKnowledge::run( @@ -122,12 +158,24 @@ public function forgetMemory(string $memoryId): void $this->toast('Memory Forgotten', 'The memory was removed from the brain index.', 'warning'); } + /** + * Refresh the visible memory results and emit a dashboard notification. + * + * @example + * Livewire::test(BrainExplorer::class, ['workspaceId' => 7])->call('refreshExplorer'); + */ public function refreshExplorer(): void { $this->searchMemories(); $this->dispatch('notify', message: 'Brain explorer refreshed'); } + /** + * Map a memory type to the badge variant used in the explorer UI. + * + * @example + * $variant = $component->typeBadgeVariant('decision'); + */ public function typeBadgeVariant(string $type): string { return match ($type) { @@ -138,6 +186,12 @@ public function typeBadgeVariant(string $type): string }; } + /** + * Load the latest memories when the explorer opens or resets. + * + * @example + * $this->loadRecentMemories(); + */ private function loadRecentMemories(): void { if ($this->workspaceId <= 0) { @@ -238,6 +292,12 @@ private function normaliseMemory(array|BrainMemory $memory): array ]; } + /** + * Resolve the Blade template used by the brain explorer screen. + * + * @example + * $path = $this->viewPath(); + */ protected function viewPath(): string { return __DIR__.'/../../resources/views/livewire/agentic/brain-explorer.blade.php'; diff --git a/php/Agentic/Livewire/CreditLedger.php b/php/Agentic/Livewire/CreditLedger.php index cc08ee12..b19b27a5 100644 --- a/php/Agentic/Livewire/CreditLedger.php +++ b/php/Agentic/Livewire/CreditLedger.php @@ -18,6 +18,12 @@ #[Title('Credit Ledger')] #[Layout('hub::admin.layouts.app')] +/** + * Review balances and post manual credit adjustments for workspace agents. + * + * @example + * Livewire::test(CreditLedger::class, ['workspaceId' => 7])->call('refreshLedger'); + */ class CreditLedger extends HubComponent { public int $workspaceId = 0; @@ -30,6 +36,12 @@ class CreditLedger extends HubComponent public string $adjustmentReason = ''; + /** + * Initialise the ledger with access checks and a selected agent. + * + * @example + * $component->mount(7); + */ public function mount(?int $workspaceId = null): void { $this->checkHadesAccess(); @@ -38,6 +50,12 @@ public function mount(?int $workspaceId = null): void } #[Computed] + /** + * Return the workspace agents available for ledger inspection. + * + * @example + * $agents = $component->agents(); + */ public function agents(): array { if ($this->workspaceId <= 0) { @@ -63,6 +81,12 @@ public function agents(): array } #[Computed] + /** + * Return the current balance summary for the selected agent. + * + * @example + * $balance = $component->balance(); + */ public function balance(): array { if ($this->workspaceId <= 0 || $this->selectedAgentId === '') { @@ -85,6 +109,12 @@ public function balance(): array } #[Computed] + /** + * Return recent credit transactions for the selected agent. + * + * @example + * $transactions = $component->transactions(); + */ public function transactions(): array { if ($this->workspaceId <= 0 || $this->selectedAgentId === '') { @@ -109,6 +139,12 @@ public function transactions(): array } #[Computed] + /** + * Summarise the visible transaction totals for the ledger screen. + * + * @example + * $totals = $component->totals(); + */ public function totals(): array { $transactions = collect($this->transactions); @@ -120,6 +156,12 @@ public function totals(): array ]; } + /** + * Add credits to the selected agent and refresh the ledger. + * + * @example + * Livewire::test(CreditLedger::class, ['workspaceId' => 7])->call('refundCredits'); + */ public function refundCredits(): void { $this->validateAdjustment(); @@ -138,6 +180,12 @@ public function refundCredits(): void $this->toast('Credits Refunded', "Added credit to {$this->selectedAgentId}.", 'success'); } + /** + * Deduct credits from the selected agent and refresh the ledger. + * + * @example + * Livewire::test(CreditLedger::class, ['workspaceId' => 7])->call('deductCredits'); + */ public function deductCredits(): void { $this->validateAdjustment(); @@ -156,6 +204,12 @@ public function deductCredits(): void $this->toast('Credits Deducted', "Deducted credit from {$this->selectedAgentId}.", 'warning'); } + /** + * Refresh the computed ledger data and resynchronise the selected agent. + * + * @example + * Livewire::test(CreditLedger::class, ['workspaceId' => 7])->call('refreshLedger'); + */ public function refreshLedger(): void { unset($this->agents, $this->balance, $this->transactions, $this->totals); @@ -163,11 +217,23 @@ public function refreshLedger(): void $this->dispatch('notify', message: 'Credit ledger refreshed'); } + /** + * Map a credit amount to the badge variant used in the ledger UI. + * + * @example + * $variant = $component->amountBadgeVariant(-5); + */ public function amountBadgeVariant(int $amount): string { return $amount >= 0 ? 'success' : 'danger'; } + /** + * Validate the current manual adjustment form values. + * + * @example + * $this->validateAdjustment(); + */ private function validateAdjustment(): void { $this->validate([ @@ -185,12 +251,24 @@ private function validateAdjustment(): void ]); } + /** + * Reset the manual adjustment form back to its default values. + * + * @example + * $this->resetAdjustment(); + */ private function resetAdjustment(): void { $this->adjustmentAmount = 1; $this->adjustmentReason = ''; } + /** + * Keep the selected agent aligned with the current workspace agent list. + * + * @example + * $this->syncSelectedAgentId(); + */ private function syncSelectedAgentId(): void { if ($this->selectedAgentId !== '' && collect($this->agents)->contains('agent_id', $this->selectedAgentId)) { @@ -200,6 +278,12 @@ private function syncSelectedAgentId(): void $this->selectedAgentId = (string) (collect($this->agents)->first()['agent_id'] ?? ''); } + /** + * Resolve the Blade template used by the credit ledger screen. + * + * @example + * $path = $this->viewPath(); + */ protected function viewPath(): string { return __DIR__.'/../../resources/views/livewire/agentic/credit-ledger.blade.php'; diff --git a/php/Agentic/Livewire/FleetOverview.php b/php/Agentic/Livewire/FleetOverview.php index 72216e2a..6bdeda01 100644 --- a/php/Agentic/Livewire/FleetOverview.php +++ b/php/Agentic/Livewire/FleetOverview.php @@ -17,6 +17,12 @@ #[Title('Fleet Overview')] #[Layout('hub::admin.layouts.app')] +/** + * Inspect workspace fleet state and dispatch new tasks to agents. + * + * @example + * Livewire::test(FleetOverview::class, ['workspaceId' => 7])->call('refreshOverview'); + */ class FleetOverview extends HubComponent { public int $workspaceId = 0; @@ -37,6 +43,12 @@ class FleetOverview extends HubComponent public string $dispatchModel = ''; + /** + * Initialise the fleet screen with access checks and a default agent. + * + * @example + * $component->mount(7); + */ public function mount(?int $workspaceId = null): void { $this->checkHadesAccess(); @@ -45,6 +57,12 @@ public function mount(?int $workspaceId = null): void } #[Computed] + /** + * Return the fleet nodes visible for the current workspace filters. + * + * @example + * $nodes = $component->nodes(); + */ public function nodes(): array { if ($this->workspaceId <= 0) { @@ -81,6 +99,12 @@ public function nodes(): array } #[Computed] + /** + * Return aggregated fleet statistics for the current workspace. + * + * @example + * $stats = $component->stats(); + */ public function stats(): array { if ($this->workspaceId <= 0) { @@ -120,6 +144,12 @@ public function stats(): array } #[Computed] + /** + * Return the list of platforms present in the current workspace fleet. + * + * @example + * $platforms = $component->platforms(); + */ public function platforms(): array { if ($this->workspaceId <= 0) { @@ -140,12 +170,24 @@ public function platforms(): array } } + /** + * Pre-fill the dispatch form for a chosen agent. + * + * @example + * Livewire::test(FleetOverview::class, ['workspaceId' => 7])->call('stageDispatch', 'agent-6'); + */ public function stageDispatch(string $agentId): void { $this->dispatchAgentId = $agentId; $this->toast('Dispatch Ready', "Prepared dispatch for {$agentId}.", 'info'); } + /** + * Validate and queue a new task dispatch for the selected agent. + * + * @example + * Livewire::test(FleetOverview::class, ['workspaceId' => 7])->call('dispatchTask'); + */ public function dispatchTask(): void { $this->validate([ @@ -185,6 +227,12 @@ public function dispatchTask(): void $this->toast('Task Dispatched', "Queued work for {$agentId}.", 'success'); } + /** + * Refresh computed fleet data and resynchronise the dispatch target. + * + * @example + * Livewire::test(FleetOverview::class, ['workspaceId' => 7])->call('refreshOverview'); + */ public function refreshOverview(): void { unset($this->nodes, $this->stats, $this->platforms); @@ -192,6 +240,12 @@ public function refreshOverview(): void $this->dispatch('notify', message: 'Fleet overview refreshed'); } + /** + * Map a fleet node status to the badge variant used in the UI. + * + * @example + * $variant = $component->statusBadgeVariant(FleetNode::STATUS_BUSY); + */ public function statusBadgeVariant(string $status): string { return match ($status) { @@ -202,6 +256,12 @@ public function statusBadgeVariant(string $status): string }; } + /** + * Keep the dispatch agent aligned with the currently visible node list. + * + * @example + * $this->syncDispatchAgentId(); + */ private function syncDispatchAgentId(): void { if ($this->dispatchAgentId !== '' && collect($this->nodes)->contains('agent_id', $this->dispatchAgentId)) { @@ -231,6 +291,12 @@ private function summariseBudget(array $budget): string ->implode(', '); } + /** + * Resolve the Blade template used by the fleet overview screen. + * + * @example + * $path = $this->viewPath(); + */ protected function viewPath(): string { return __DIR__.'/../../resources/views/livewire/agentic/fleet-overview.blade.php'; diff --git a/php/Agentic/Livewire/HubComponent.php b/php/Agentic/Livewire/HubComponent.php index fc5ab428..73f823e3 100644 --- a/php/Agentic/Livewire/HubComponent.php +++ b/php/Agentic/Livewire/HubComponent.php @@ -25,11 +25,23 @@ */ abstract class HubComponent extends Component { + /** + * Render the Livewire screen from its resolved Blade file. + * + * @example + * $view = $component->render(); + */ public function render(): View { return view()->file($this->viewPath()); } + /** + * Resolve the current workspace identifier for the admin screen. + * + * @example + * $workspaceId = $this->resolveWorkspaceId(); + */ protected function resolveWorkspaceId(): int { try { @@ -39,6 +51,12 @@ protected function resolveWorkspaceId(): int } } + /** + * Abort the request when the current user lacks Hades access. + * + * @example + * $this->checkHadesAccess(); + */ protected function checkHadesAccess(): void { if (! auth()->user()?->isHades()) { @@ -46,6 +64,12 @@ protected function checkHadesAccess(): void } } + /** + * Dispatch a toast notification through Flux or the fallback event bus. + * + * @example + * $this->toast('Saved', 'Ledger refreshed.', 'success'); + */ protected function toast(string $heading, string $text, string $variant): void { if (class_exists(Flux::class)) { diff --git a/php/Mcp/Console/McpAgentServerCommand.php b/php/Mcp/Console/McpAgentServerCommand.php index 6ecd93ea..e0150a7a 100644 --- a/php/Mcp/Console/McpAgentServerCommand.php +++ b/php/Mcp/Console/McpAgentServerCommand.php @@ -15,12 +15,24 @@ use RuntimeException; use Throwable; +/** + * Run the MCP agent server over stdio for JSON-RPC requests. + * + * @example + * php artisan mcp:agent-server + */ class McpAgentServerCommand extends Command { protected $signature = 'mcp:agent-server'; protected $description = 'Run the MCP agent server in stdio mode'; + /** + * Process the stdio request loop and emit JSON-RPC responses. + * + * @example + * $exitCode = $this->handle($toolRegistry, $quotaService, $queryAuditService); + */ public function handle( ToolRegistry $toolRegistry, McpQuotaService $quotaService, @@ -82,6 +94,12 @@ public function handle( return self::SUCCESS; } + /** + * Resolve the stream path from an environment override or default value. + * + * @example + * $path = $this->streamPath('MCP_AGENT_SERVER_INPUT', 'php://stdin'); + */ private function streamPath(string $variable, string $default): string { $value = getenv($variable); @@ -89,6 +107,12 @@ private function streamPath(string $variable, string $default): string return is_string($value) && $value !== '' ? $value : $default; } + /** + * Decode a JSON-RPC payload and route batch or single requests. + * + * @example + * $response = $this->processPayload('{"jsonrpc":"2.0","method":"ping","id":1}', $toolRegistry, $quotaService, $queryAuditService); + */ private function processPayload( string $payload, ToolRegistry $toolRegistry, @@ -136,6 +160,12 @@ private function processPayload( ); } + /** + * Execute a single JSON-RPC request and build the matching response envelope. + * + * @example + * $response = $this->processRequest(['jsonrpc' => '2.0', 'method' => 'ping', 'id' => 1], $toolRegistry, $quotaService, $queryAuditService); + */ private function processRequest( array $request, ToolRegistry $toolRegistry, @@ -202,6 +232,12 @@ private function processRequest( return $response; } + /** + * Run a tool call after quota and audit checks have passed. + * + * @example + * $response = $this->handleToolCall(['name' => 'brain_list', 'arguments' => []], 7, $toolRegistry, $quotaService, $queryAuditService); + */ private function handleToolCall( array $params, mixed $id, @@ -304,6 +340,12 @@ private function handleToolCall( } } + /** + * Resolve the requested tool name from supported JSON-RPC parameter keys. + * + * @example + * $toolName = $this->resolveToolName(['tool' => 'brain_list']); + */ private function resolveToolName(array $params): string|null { $value = $params['name'] ?? $params['tool'] ?? null; @@ -311,6 +353,12 @@ private function resolveToolName(array $params): string|null return is_string($value) && $value !== '' ? $value : null; } + /** + * Determine the workspace identifier carried by the request payload. + * + * @example + * $workspaceId = $this->workspaceId(['workspace_id' => '42'], []); + */ private function workspaceId(array $params, array $arguments): string { $value = $params['workspace_id'] @@ -322,6 +370,12 @@ private function workspaceId(array $params, array $arguments): string return (string) $value; } + /** + * Extract a free-form query string from tool arguments when present. + * + * @example + * $query = $this->toolQuery(['query' => 'select * from memories']); + */ private function toolQuery(array $arguments): string|null { $query = $arguments['query'] ?? null; @@ -329,6 +383,12 @@ private function toolQuery(array $arguments): string|null return is_string($query) && $query !== '' ? $query : null; } + /** + * Estimate the number of result items returned by a tool call. + * + * @example + * $count = $this->resultCount(['id' => 1, 'id' => 2]); + */ private function resultCount(mixed $result): int { if (is_array($result)) { @@ -338,6 +398,12 @@ private function resultCount(mixed $result): int return $result === null ? 0 : 1; } + /** + * Persist query audit details when the audit table is available. + * + * @example + * $this->recordAudit($queryAuditService, 'select 1', '42', 'brain_list', 12, ['transport' => 'stdio'], 1); + */ private function recordAudit( QueryAuditService $queryAuditService, string $query, @@ -360,6 +426,12 @@ private function recordAudit( } } + /** + * Build the standard JSON-RPC success response structure. + * + * @example + * $response = $this->successResponse(['ok' => true], 1); + */ private function successResponse(mixed $result, mixed $id): array { return [ @@ -369,6 +441,12 @@ private function successResponse(mixed $result, mixed $id): array ]; } + /** + * Build the standard JSON-RPC error response structure. + * + * @example + * $response = $this->errorResponse(-32601, 'Method not found', 1); + */ private function errorResponse(int $code, string $message, mixed $id = null, array $data = []): array { $error = [ @@ -387,6 +465,12 @@ private function errorResponse(int $code, string $message, mixed $id = null, arr ]; } + /** + * Encode a response payload as JSON for the stdio transport. + * + * @example + * $json = $this->encodePayload(['jsonrpc' => '2.0', 'result' => ['ok' => true], 'id' => 1]); + */ private function encodePayload(mixed $payload): string { $encoded = json_encode( diff --git a/php/Mcp/Services/DataRedactor.php b/php/Mcp/Services/DataRedactor.php index 150b4899..3f3f4621 100644 --- a/php/Mcp/Services/DataRedactor.php +++ b/php/Mcp/Services/DataRedactor.php @@ -6,6 +6,13 @@ namespace Core\Mcp\Services; +/** + * Redact sensitive values from MCP payloads before they are logged or shown. + * + * @example + * $redactor = new DataRedactor(); + * $safe = $redactor->redact(['token' => 'sk_live_secret']); + */ final class DataRedactor { protected const REDACTED = '[REDACTED]'; @@ -62,6 +69,12 @@ final class DataRedactor 'licence', ]; + /** + * Recursively redact secrets and personal data from a value. + * + * @example + * $safe = $redactor->redact(['authorization' => 'Bearer sk_live_secret']); + */ public function redact(mixed $data, int $maxDepth = 10): mixed { if ($maxDepth <= 0) { @@ -83,6 +96,12 @@ public function redact(mixed $data, int $maxDepth = 10): mixed return $data; } + /** + * Produce a shortened, redacted preview of a value for dashboards. + * + * @example + * $preview = $redactor->summarize(['email' => 'agent@example.com', 'notes' => 'Long body text']); + */ public function summarize(mixed $data, int $maxDepth = 3): mixed { if ($maxDepth <= 0) { @@ -135,6 +154,12 @@ public function summarize(mixed $data, int $maxDepth = 3): mixed return $data; } + /** + * Redact a structured object after normalising it to array-like data. + * + * @example + * $safe = $this->redactObject((object) ['token' => 'sk_live_secret'], 3); + */ protected function redactObject(object $data, int $maxDepth): mixed { $normalised = $this->normaliseObject($data); @@ -152,6 +177,12 @@ protected function redactObject(object $data, int $maxDepth): mixed : $normalised; } + /** + * Summarise a structured object after normalising it to array-like data. + * + * @example + * $summary = $this->summarizeObject((object) ['email' => 'agent@example.com'], 2); + */ protected function summarizeObject(object $data, int $maxDepth): mixed { $normalised = $this->normaliseObject($data); @@ -163,6 +194,12 @@ protected function summarizeObject(object $data, int $maxDepth): mixed return $this->summarize($normalised, $maxDepth); } + /** + * Redact one associative array while preserving non-sensitive keys. + * + * @example + * $safe = $this->redactArray(['password' => 'secret', 'status' => 'ok'], 2); + */ protected function redactArray(array $data, int $maxDepth): array { $result = []; @@ -198,6 +235,12 @@ protected function redactArray(array $data, int $maxDepth): array return $result; } + /** + * Decide whether an array key should always be fully redacted. + * + * @example + * $sensitive = $this->isSensitiveKey('api_key'); + */ protected function isSensitiveKey(string $key): bool { foreach (self::SENSITIVE_KEYS as $sensitiveKey) { @@ -209,6 +252,12 @@ protected function isSensitiveKey(string $key): bool return false; } + /** + * Decide whether an array key should receive partial personal-data redaction. + * + * @example + * $pii = $this->isPiiKey('email'); + */ protected function isPiiKey(string $key): bool { foreach (self::PII_KEYS as $piiKey) { @@ -220,6 +269,12 @@ protected function isPiiKey(string $key): bool return false; } + /** + * Scrub sensitive token formats from a free-form string. + * + * @example + * $safe = $this->redactString('Bearer sk_live_secret'); + */ protected function redactString(string $value): string { $value = preg_replace('/Bearer\s+[A-Za-z0-9\-_\.]+/i', 'Bearer '.self::REDACTED, $value) ?? $value; @@ -232,6 +287,12 @@ protected function redactString(string $value): string return $value; } + /** + * Partially mask a personally identifiable string while leaving a hint. + * + * @example + * $masked = $this->partialRedact('agent@example.com'); + */ protected function partialRedact(string $value): string { $length = strlen($value); @@ -249,6 +310,12 @@ protected function partialRedact(string $value): string return substr($value, 0, $visible).'***'.substr($value, -$visible); } + /** + * Convert an object into data that can be traversed by the redactor. + * + * @example + * $normalised = $this->normaliseObject((object) ['token' => 'sk_live_secret']); + */ protected function normaliseObject(object $data): mixed { if ($data instanceof \JsonSerializable) { diff --git a/php/Mcp/Services/McpHealthService.php b/php/Mcp/Services/McpHealthService.php index 0fa565a5..2b46ac66 100644 --- a/php/Mcp/Services/McpHealthService.php +++ b/php/Mcp/Services/McpHealthService.php @@ -9,6 +9,13 @@ use Illuminate\Support\Facades\Cache; use Symfony\Component\Yaml\Yaml; +/** + * Check registered MCP servers and cache their health status. + * + * @example + * $service = new McpHealthService(); + * $status = $service->check('host-hub'); + */ final class McpHealthService { public const STATUS_ONLINE = 'online'; @@ -23,6 +30,12 @@ final class McpHealthService protected int $timeout = 5; + /** + * Check one MCP server and optionally bypass the cached status. + * + * @example + * $status = $service->check('host-hub', true); + */ public function check(string $serverId, bool $forceRefresh = false): array { $cacheKey = sprintf('mcp:health:%s', $serverId); @@ -46,6 +59,12 @@ public function check(string $serverId, bool $forceRefresh = false): array return $result; } + /** + * Check every registered MCP server and return their status map. + * + * @example + * $statuses = $service->checkAll(); + */ public function checkAll(bool $forceRefresh = false): array { $results = []; @@ -57,6 +76,12 @@ public function checkAll(bool $forceRefresh = false): array return $results; } + /** + * Return a cached health result for one MCP server when present. + * + * @example + * $cached = $service->getCachedStatus('host-hub'); + */ public function getCachedStatus(string $serverId): ?array { $status = Cache::get(sprintf('mcp:health:%s', $serverId)); @@ -64,11 +89,23 @@ public function getCachedStatus(string $serverId): ?array return is_array($status) ? $status : null; } + /** + * Remove the cached health entry for one MCP server. + * + * @example + * $service->clearCache('host-hub'); + */ public function clearCache(string $serverId): void { Cache::forget(sprintf('mcp:health:%s', $serverId)); } + /** + * Remove cached health entries for all registered MCP servers. + * + * @example + * $service->clearAllCache(); + */ public function clearAllCache(): void { foreach ($this->registeredServers() as $serverId) { @@ -76,6 +113,12 @@ public function clearAllCache(): void } } + /** + * Render an HTML badge for a resolved MCP server status. + * + * @example + * $badge = $service->getStatusBadge(McpHealthService::STATUS_ONLINE); + */ public function getStatusBadge(string $status): string { return match ($status) { @@ -86,6 +129,12 @@ public function getStatusBadge(string $status): string }; } + /** + * Map a server status to the dashboard colour token. + * + * @example + * $colour = $service->getStatusColour(McpHealthService::STATUS_DEGRADED); + */ public function getStatusColour(string $status): string { return match ($status) { @@ -96,6 +145,12 @@ public function getStatusColour(string $status): string }; } + /** + * Probe one server definition and derive its health result. + * + * @example + * $result = $this->pingServer(['connection' => ['type' => 'stdio', 'command' => 'php', 'args' => ['artisan', 'mcp:agent-server']]]); + */ protected function pingServer(array $server): array { $connection = (array) ($server['connection'] ?? []); @@ -164,6 +219,12 @@ protected function pingServer(array $server): array ]); } + /** + * Execute a server process and capture its output, error, and timing. + * + * @example + * $result = $this->executeProcess(['php', 'artisan', 'mcp:agent-server'], base_path(), "{\"jsonrpc\":\"2.0\",\"method\":\"ping\",\"id\":1}\n"); + */ protected function executeProcess(array $command, string $cwd, string $input): array { $descriptors = [ @@ -229,6 +290,12 @@ protected function executeProcess(array $command, string $cwd, string $input): a ]; } + /** + * Build a normalised health result payload for dashboard consumers. + * + * @example + * $result = $this->buildResult(self::STATUS_ONLINE, 'Server responding', ['response_time_ms' => 42]); + */ protected function buildResult(string $status, string $message, array $extra = []): array { return array_merge([ @@ -238,6 +305,12 @@ protected function buildResult(string $status, string $message, array $extra = [ ], array_filter($extra, static fn (mixed $value): bool => $value !== null)); } + /** + * Return the server identifiers listed in the MCP registry. + * + * @example + * $serverIds = $this->registeredServers(); + */ protected function registeredServers(): array { $servers = $this->loadRegistry()['servers'] ?? []; @@ -248,6 +321,12 @@ protected function registeredServers(): array ))); } + /** + * Load the top-level MCP registry document from resources. + * + * @example + * $registry = $this->loadRegistry(); + */ protected function loadRegistry(): array { $path = resource_path('mcp/registry.yaml'); @@ -255,6 +334,12 @@ protected function loadRegistry(): array return file_exists($path) ? (array) Yaml::parseFile($path) : ['servers' => []]; } + /** + * Load one server definition from the MCP resources directory. + * + * @example + * $server = $this->loadServerConfig('host-hub'); + */ protected function loadServerConfig(string $serverId): ?array { $path = resource_path(sprintf('mcp/servers/%s.yaml', $serverId)); @@ -262,6 +347,12 @@ protected function loadServerConfig(string $serverId): ?array return file_exists($path) ? (array) Yaml::parseFile($path) : null; } + /** + * Resolve `${NAME}` placeholders inside registry values from the environment. + * + * @example + * $command = $this->resolveEnvVars('${PHP_BINARY:-php}'); + */ protected function resolveEnvVars(string $value): string { return preg_replace_callback('/\$\{([^}]+)\}/', static function (array $matches): string { diff --git a/php/Mcp/Services/McpMetricsService.php b/php/Mcp/Services/McpMetricsService.php index 260a53d4..f8230ad0 100644 --- a/php/Mcp/Services/McpMetricsService.php +++ b/php/Mcp/Services/McpMetricsService.php @@ -12,12 +12,25 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +/** + * Aggregate MCP tool activity into dashboard-friendly metrics. + * + * @example + * $service = new McpMetricsService(); + * $overview = $service->getOverview(7); + */ final class McpMetricsService { protected string $statsTable = 'mcp_tool_call_stats'; protected string $callsTable = 'mcp_tool_calls'; + /** + * Summarise tool activity for the requested reporting window. + * + * @example + * $overview = $service->getOverview(14); + */ public function getOverview(int $days = 7): array { $currentStart = CarbonImmutable::now()->subDays($days - 1)->startOfDay(); @@ -47,6 +60,12 @@ public function getOverview(int $days = 7): array ]; } + /** + * Return the per-day call trend for the requested reporting window. + * + * @example + * $trend = $service->getDailyTrend(7); + */ public function getDailyTrend(int $days = 7): Collection { $result = collect(); @@ -74,6 +93,12 @@ public function getDailyTrend(int $days = 7): Collection return $result; } + /** + * Return the most-used tools in the requested reporting window. + * + * @example + * $tools = $service->getTopTools(7, 5); + */ public function getTopTools(int $days = 7, int $limit = 10): Collection { if (! Schema::hasTable($this->statsTable)) { @@ -109,6 +134,12 @@ public function getTopTools(int $days = 7, int $limit = 10): Collection }); } + /** + * Return aggregated call statistics grouped by MCP server. + * + * @example + * $servers = $service->getServerStats(30); + */ public function getServerStats(int $days = 7): Collection { if (! Schema::hasTable($this->statsTable)) { @@ -143,6 +174,12 @@ public function getServerStats(int $days = 7): Collection }); } + /** + * Return the latest recorded MCP calls for dashboard activity feeds. + * + * @example + * $calls = $service->getRecentCalls(20); + */ public function getRecentCalls(int $limit = 20): Collection { if (! Schema::hasTable($this->callsTable)) { @@ -173,6 +210,12 @@ public function getRecentCalls(int $limit = 20): Collection }); } + /** + * Group recent tool failures by tool name and error code. + * + * @example + * $errors = $service->getErrorBreakdown(7); + */ public function getErrorBreakdown(int $days = 7): Collection { if (! Schema::hasTable($this->callsTable)) { @@ -194,6 +237,12 @@ public function getErrorBreakdown(int $days = 7): Collection ])); } + /** + * Calculate duration percentiles for successful tool calls. + * + * @example + * $performance = $service->getToolPerformance(7, 10); + */ public function getToolPerformance(int $days = 7, int $limit = 10): Collection { if (! Schema::hasTable($this->callsTable)) { @@ -225,6 +274,12 @@ public function getToolPerformance(int $days = 7, int $limit = 10): Collection })->sortByDesc('call_count')->take($limit)->values(); } + /** + * Bucket recent tool calls by hour for the last 24 hours. + * + * @example + * $hours = $service->getHourlyDistribution(); + */ public function getHourlyDistribution(): Collection { $distribution = collect(); @@ -255,6 +310,12 @@ public function getHourlyDistribution(): Collection return $distribution; } + /** + * Return activity totals grouped by plan slug for the requested window. + * + * @example + * $plans = $service->getPlanActivity(7, 10); + */ public function getPlanActivity(int $days = 7, int $limit = 10): Collection { if (! Schema::hasTable($this->callsTable)) { @@ -286,6 +347,12 @@ public function getPlanActivity(int $days = 7, int $limit = 10): Collection }); } + /** + * Load raw stats rows for the supplied date range. + * + * @example + * $rows = $this->statsInRange(CarbonImmutable::now()->subDays(6)->startOfDay(), CarbonImmutable::now()->endOfDay()); + */ protected function statsInRange(CarbonImmutable $start, CarbonImmutable $end): Collection { if (! Schema::hasTable($this->statsTable)) { @@ -298,6 +365,12 @@ protected function statsInRange(CarbonImmutable $start, CarbonImmutable $end): C ->map(static fn (object $row): Collection => collect((array) $row)); } + /** + * Load grouped daily totals for the supplied date range. + * + * @example + * $rows = $this->dailyTrendRows(CarbonImmutable::now()->subDays(6)->startOfDay(), CarbonImmutable::now()->endOfDay()); + */ protected function dailyTrendRows(CarbonImmutable $start, CarbonImmutable $end): Collection { if (! Schema::hasTable($this->statsTable)) { @@ -315,6 +388,12 @@ protected function dailyTrendRows(CarbonImmutable $start, CarbonImmutable $end): ->get(); } + /** + * Interpolate a percentile value from an ascending list of durations. + * + * @example + * $p95 = $this->percentile(collect([10, 25, 40, 90]), 95); + */ protected function percentile(Collection $sortedValues, int $percentile): float { $count = $sortedValues->count(); @@ -336,6 +415,12 @@ protected function percentile(Collection $sortedValues, int $percentile): float return round(((float) $sortedValues[$lower]) + (((float) $sortedValues[$upper] - (float) $sortedValues[$lower]) * $fraction), 1); } + /** + * Render a millisecond duration using the dashboard display format. + * + * @example + * $label = $this->humanDuration(1250); + */ protected function humanDuration(?int $durationMs): string { if ($durationMs === null || $durationMs <= 0) { diff --git a/php/Mcp/Services/OpenApiGenerator.php b/php/Mcp/Services/OpenApiGenerator.php index bbb98efd..c7b87de4 100644 --- a/php/Mcp/Services/OpenApiGenerator.php +++ b/php/Mcp/Services/OpenApiGenerator.php @@ -9,12 +9,25 @@ use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; +/** + * Generate an OpenAPI document for the MCP HTTP surface. + * + * @example + * $generator = new OpenApiGenerator(); + * $schema = $generator->generate(); + */ final class OpenApiGenerator { protected array $registry = ['servers' => []]; protected array $servers = []; + /** + * Build the full OpenAPI document from the MCP registry. + * + * @example + * $schema = $generator->generate(); + */ public function generate(): array { $this->loadRegistry(); @@ -30,16 +43,34 @@ public function generate(): array ]; } + /** + * Encode the generated OpenAPI document as formatted JSON. + * + * @example + * $json = $generator->toJson(); + */ public function toJson(): string { return (string) json_encode($this->generate(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } + /** + * Encode the generated OpenAPI document as YAML. + * + * @example + * $yaml = $generator->toYaml(); + */ public function toYaml(): string { return Yaml::dump($this->generate(), 10, 2); } + /** + * Load the MCP registry file that declares available servers. + * + * @example + * $this->loadRegistry(); + */ protected function loadRegistry(): void { $path = resource_path('mcp/registry.yaml'); @@ -56,6 +87,12 @@ protected function loadRegistry(): void } } + /** + * Load server definition files referenced by the MCP registry. + * + * @example + * $this->loadServers(); + */ protected function loadServers(): void { $this->servers = []; @@ -81,6 +118,12 @@ protected function loadServers(): void } } + /** + * Build the OpenAPI info block for the MCP HTTP API. + * + * @example + * $info = $this->buildInfo(); + */ protected function buildInfo(): array { return [ @@ -98,6 +141,12 @@ protected function buildInfo(): array ]; } + /** + * Return the OpenAPI server list for production and local environments. + * + * @example + * $servers = $this->buildServers(); + */ protected function buildServers(): array { return [ @@ -112,6 +161,12 @@ protected function buildServers(): array ]; } + /** + * Build tag definitions for discovery and execution endpoints. + * + * @example + * $tags = $this->buildTags(); + */ protected function buildTags(): array { $tags = [ @@ -129,6 +184,12 @@ protected function buildTags(): array return $tags; } + /** + * Build the path map for the MCP HTTP API. + * + * @example + * $paths = $this->buildPaths(); + */ protected function buildPaths(): array { return [ @@ -223,6 +284,12 @@ protected function buildPaths(): array ]; } + /** + * Build a secured GET operation definition for the OpenAPI document. + * + * @example + * $operation = $this->authenticatedGet('Discovery', 'List all MCP servers', 'listServers', ['200' => ['description' => 'OK']]); + */ protected function authenticatedGet( string $tag, string $summary, @@ -245,6 +312,12 @@ protected function authenticatedGet( return $operation; } + /** + * Build a secured POST operation definition for the OpenAPI document. + * + * @example + * $operation = $this->authenticatedPost('Execution', 'Execute an MCP tool', 'callTool', '#/components/schemas/ToolCallRequest', ['200' => ['description' => 'OK']]); + */ protected function authenticatedPost( string $tag, string $summary, @@ -265,11 +338,23 @@ protected function authenticatedPost( ]; } + /** + * Return the supported authentication schemes for every endpoint. + * + * @example + * $security = $this->securityRequirements(); + */ protected function securityRequirements(): array { return [['bearerAuth' => []], ['apiKeyAuth' => []]]; } + /** + * Build a required string parameter schema for a path or query field. + * + * @example + * $parameter = $this->requiredStringParameter('serverId', 'path'); + */ protected function requiredStringParameter(string $name, string $location): array { return [ @@ -280,6 +365,12 @@ protected function requiredStringParameter(string $name, string $location): arra ]; } + /** + * Build a response definition that points to a shared schema reference. + * + * @example + * $response = $this->schemaResponse('Server details', '#/components/schemas/Server'); + */ protected function schemaResponse(string $description, string $schemaRef): array { return [ @@ -288,6 +379,12 @@ protected function schemaResponse(string $description, string $schemaRef): array ]; } + /** + * Build an `application/json` content block for a schema reference. + * + * @example + * $content = $this->jsonSchemaContent('#/components/schemas/ToolCallResponse'); + */ protected function jsonSchemaContent(string $schemaRef): array { return [ @@ -297,6 +394,12 @@ protected function jsonSchemaContent(string $schemaRef): array ]; } + /** + * Build shared schema and security component definitions. + * + * @example + * $components = $this->buildComponents(); + */ protected function buildComponents(): array { return [ diff --git a/php/Mcp/Services/QueryAuditService.php b/php/Mcp/Services/QueryAuditService.php index 163d02c4..60ae1354 100644 --- a/php/Mcp/Services/QueryAuditService.php +++ b/php/Mcp/Services/QueryAuditService.php @@ -15,10 +15,23 @@ use InvalidArgumentException; use RuntimeException; +/** + * Audit MCP query usage and expose filtered activity summaries. + * + * @example + * $service = new QueryAuditService(); + * $entry = $service->log('select * from memories', ['workspace_id' => '42']); + */ final class QueryAuditService { private const TABLE = 'mcp_audit_entries'; + /** + * Decide whether a query looks safe enough to execute. + * + * @example + * $safe = $service->isSafe('select * from memories'); + */ public function isSafe(string $query): bool { $trimmedQuery = ltrim($query); @@ -31,11 +44,23 @@ public function isSafe(string $query): bool return ! $startsWithWriteStatement && ! $callsDangerousFunction; } + /** + * Decide whether an encoded result payload exceeds the audit size limit. + * + * @example + * $tooLarge = $service->exceedsLimit(['rows' => range(1, 5000)], 1024); + */ public function exceedsLimit(array $result, int $limitBytes = 1000000): bool { return strlen((string) json_encode($result, JSON_INVALID_UTF8_SUBSTITUTE)) > $limitBytes; } + /** + * Persist one audit entry for a query execution. + * + * @example + * $entry = $service->log('select * from memories', ['workspace_id' => '42', 'tool_name' => 'brain_list']); + */ public function log(string $query, array $context = []): AuditEntry { $this->ensureTableExists(); @@ -179,6 +204,12 @@ static function (array $bucket): array { return $aggregates; } + /** + * Assert that the audit table exists before reading or writing entries. + * + * @example + * $this->ensureTableExists(); + */ private function ensureTableExists(): void { if (! Schema::hasTable(self::TABLE)) { @@ -186,6 +217,12 @@ private function ensureTableExists(): void } } + /** + * Normalise a recorded-at value into an immutable timestamp. + * + * @example + * $recordedAt = $this->resolveRecordedAt('2026-04-27T10:15:00Z'); + */ private function resolveRecordedAt(mixed $value): CarbonImmutable { if ($value instanceof CarbonImmutable) { @@ -203,6 +240,12 @@ private function resolveRecordedAt(mixed $value): CarbonImmutable return CarbonImmutable::parse((string) $value); } + /** + * Validate and return a supported aggregation period. + * + * @example + * $period = $this->resolvePeriod('hour'); + */ private function resolvePeriod(string $period): string { if (! in_array($period, ['minute', 'hour', 'day'], true)) { @@ -215,6 +258,12 @@ private function resolvePeriod(string $period): string return $period; } + /** + * Format one aggregation bucket key for the requested period. + * + * @example + * $bucket = $this->bucketFor(CarbonImmutable::parse('2026-04-27 10:15:00'), 'hour'); + */ private function bucketFor(CarbonImmutable $timestamp, string $period): string { return match ($period) { @@ -224,6 +273,12 @@ private function bucketFor(CarbonImmutable $timestamp, string $period): string }; } + /** + * Resolve an audit entry model timestamp into an immutable Carbon value. + * + * @example + * $timestamp = $this->entryTimestamp($entry); + */ private function entryTimestamp(McpAuditEntry $entry): CarbonImmutable { if ($entry->created_at instanceof CarbonInterface) { @@ -234,6 +289,12 @@ private function entryTimestamp(McpAuditEntry $entry): CarbonImmutable } } +/** + * Persisted Eloquent model for one MCP audit log entry. + * + * @example + * $entry = McpAuditEntry::query()->first(); + */ class McpAuditEntry extends Model { protected $table = 'mcp_audit_entries'; diff --git a/php/Mcp/Services/ToolRateLimiter.php b/php/Mcp/Services/ToolRateLimiter.php index 67acd4f2..4668e6e5 100644 --- a/php/Mcp/Services/ToolRateLimiter.php +++ b/php/Mcp/Services/ToolRateLimiter.php @@ -64,6 +64,12 @@ public function check(string $identifier, string $toolName): array ]; } + /** + * Increment the rate-limit counter for a (caller, tool) pair. + * + * @example + * $rl->hit($apiKey, 'agent.brain.recall'); + */ public function hit(string $identifier, string $toolName): void { if (! config('mcp.rate_limiting.enabled', true)) { @@ -80,6 +86,12 @@ public function hit(string $identifier, string $toolName): void Cache::increment($cacheKey); } + /** + * Clear one rate-limit bucket or every configured bucket for a caller. + * + * @example + * $rl->clear($apiKey, 'agent.brain.recall'); + */ public function clear(string $identifier, ?string $toolName = null): void { if ($toolName !== null) { @@ -95,6 +107,12 @@ public function clear(string $identifier, ?string $toolName = null): void Cache::forget($this->cacheKey($identifier, '*')); } + /** + * Return the current limit, remaining calls, and reset timestamp. + * + * @example + * $status = $rl->getStatus($apiKey, 'agent.brain.recall'); + */ public function getStatus(string $identifier, string $toolName): array { $limit = $this->limitForTool($toolName); @@ -109,6 +127,12 @@ public function getStatus(string $identifier, string $toolName): array ]; } + /** + * Resolve the configured call limit for one tool name. + * + * @example + * $limit = $this->limitForTool('agent.brain.recall'); + */ protected function limitForTool(string $toolName): int { $perTool = (array) config('mcp.rate_limiting.per_tool', []); @@ -120,11 +144,23 @@ protected function limitForTool(string $toolName): int return (int) config('mcp.rate_limiting.calls_per_minute', 60); } + /** + * Build the cache key used for one caller and tool bucket. + * + * @example + * $key = $this->cacheKey($apiKey, 'agent.brain.recall'); + */ protected function cacheKey(string $identifier, string $toolName): string { return self::CACHE_PREFIX.$identifier.':'.$toolName; } + /** + * Read the remaining time-to-live for a cached rate-limit bucket. + * + * @example + * $ttl = $this->ttl($this->cacheKey($apiKey, 'agent.brain.recall'), 60); + */ protected function ttl(string $cacheKey, int $default): int { try { diff --git a/php/Mcp/Tools/Agent/Brain/BrainList.php b/php/Mcp/Tools/Agent/Brain/BrainList.php index c03885c5..9da41baf 100644 --- a/php/Mcp/Tools/Agent/Brain/BrainList.php +++ b/php/Mcp/Tools/Agent/Brain/BrainList.php @@ -22,6 +22,12 @@ class BrainList extends AgentTool protected array $scopes = ['read']; + /** + * Declare the workspace context required before memories can be listed. + * + * @example + * $dependencies = $tool->dependencies(); + */ public function dependencies(): array { return [ @@ -29,16 +35,34 @@ public function dependencies(): array ]; } + /** + * Return the MCP tool name exposed to callers. + * + * @example + * $name = $tool->name(); + */ public function name(): string { return 'brain_list'; } + /** + * Describe what the brain list tool returns and how it can be filtered. + * + * @example + * $description = $tool->description(); + */ public function description(): string { return 'List memories in the shared OpenBrain knowledge store. Supports filtering by organisation, project, type, and agent. No vector search -- use brain_recall for semantic queries.'; } + /** + * Return the JSON schema for the tool's supported filter arguments. + * + * @example + * $schema = $tool->inputSchema(); + */ public function inputSchema(): array { return [ @@ -73,6 +97,12 @@ public function inputSchema(): array ]; } + /** + * Query the workspace brain and return matching memory entries. + * + * @example + * $response = $tool->handle(['limit' => 10, 'type' => 'context'], ['workspace_id' => 42]); + */ public function handle(array $args, array $context = []): array { $workspaceId = $context['workspace_id'] ?? null; diff --git a/pkg/agentic/actions.go b/pkg/agentic/actions.go index a085f51c..957d8e29 100644 --- a/pkg/agentic/actions.go +++ b/pkg/agentic/actions.go @@ -8,9 +8,9 @@ package agentic import ( "context" + core "dappco.re/go" "dappco.re/go/agent/pkg/lib" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -193,9 +193,13 @@ func (s *PrepSubsystem) handlePersona(_ context.Context, options core.Options) c // // )) func (s *PrepSubsystem) handleComplete(ctx context.Context, options core.Options) core.Result { + if s == nil || s.ServiceRuntime == nil { + return core.Fail(core.E("agentic.complete", "core runtime is required", nil)) + } + c := s.Core() if c == nil { - return core.Result{Value: core.E("agentic.complete", "core runtime is required", nil), OK: false} + return core.Fail(core.E("agentic.complete", "core runtime is required", nil)) } return c.Task("agent.completion").Run(ctx, c, options) } diff --git a/pkg/agentic/actions_example_test.go b/pkg/agentic/actions_example_test.go index 1596f880..103c1815 100644 --- a/pkg/agentic/actions_example_test.go +++ b/pkg/agentic/actions_example_test.go @@ -5,7 +5,7 @@ package agentic_test import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/agent/pkg/agentic" ) diff --git a/pkg/agentic/actions_test.go b/pkg/agentic/actions_test.go index 840d467d..186cf8e7 100644 --- a/pkg/agentic/actions_test.go +++ b/pkg/agentic/actions_test.go @@ -9,11 +9,9 @@ import ( "testing" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/lib" - core "dappco.re/go/core" "dappco.re/go/forge" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestActions_HandleDispatch_Good(t *testing.T) { @@ -23,7 +21,7 @@ func TestActions_HandleDispatch_Good(t *testing.T) { core.Option{Key: "task", Value: "fix tests"}, )) // Will fail (no local clone) but exercises the handler path - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestActions_HandleDispatch_Bad_EntitlementDenied(t *testing.T) { @@ -46,10 +44,10 @@ func TestActions_HandleDispatch_Bad_EntitlementDenied(t *testing.T) { core.Option{Key: "task", Value: "fix tests"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) err, ok := r.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "dispatch limit reached") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "dispatch limit reached") } func TestActions_HandleDispatch_Good_RecordsUsage(t *testing.T) { @@ -71,15 +69,15 @@ func TestActions_HandleDispatch_Good_RecordsUsage(t *testing.T) { }) srcRepo := core.JoinPath(t.TempDir(), "core", "go-io") - require.True(t, fs.EnsureDir(srcRepo).OK) + core.RequireTrue(t, fs.EnsureDir(srcRepo).OK) process := s.Core().Process() - require.True(t, process.RunIn(context.Background(), srcRepo, "git", "init", "-b", "main").OK) - require.True(t, process.RunIn(context.Background(), srcRepo, "git", "config", "user.name", "Test").OK) - require.True(t, process.RunIn(context.Background(), srcRepo, "git", "config", "user.email", "test@test.com").OK) - require.True(t, fs.Write(core.JoinPath(srcRepo, "go.mod"), "module test\ngo 1.22\n").OK) - require.True(t, fs.Write(core.JoinPath(srcRepo, "README.md"), "hello\n").OK) - require.True(t, process.RunIn(context.Background(), srcRepo, "git", "add", ".").OK) - require.True(t, process.RunIn( + core.RequireTrue(t, process.RunIn(context.Background(), srcRepo, "git", "init", "-b", "main").OK) + core.RequireTrue(t, process.RunIn(context.Background(), srcRepo, "git", "config", "user.name", "Test").OK) + core.RequireTrue(t, process.RunIn(context.Background(), srcRepo, "git", "config", "user.email", "test@test.com").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(srcRepo, "go.mod"), "module test\ngo 1.22\n").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(srcRepo, "README.md"), "hello\n").OK) + core.RequireTrue(t, process.RunIn(context.Background(), srcRepo, "git", "add", ".").OK) + core.RequireTrue(t, process.RunIn( context.Background(), srcRepo, "git", @@ -104,15 +102,17 @@ func TestActions_HandleDispatch_Good_RecordsUsage(t *testing.T) { core.Option{Key: "dry-run", Value: true}, )) - require.Truef(t, r.OK, "dispatch failed: %#v", r.Value) - assert.Equal(t, 1, recorded) + if !r.OK { + t.Fatalf("dispatch failed: %#v", r.Value) + } + core.AssertEqual(t, 1, recorded) } func TestActions_HandleStatus_Good(t *testing.T) { t.Setenv("CORE_WORKSPACE", t.TempDir()) s := newPrepWithProcess() r := s.handleStatus(context.Background(), core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestActions_HandlePrompt_Good(t *testing.T) { @@ -120,7 +120,7 @@ func TestActions_HandlePrompt_Good(t *testing.T) { r := s.handlePrompt(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: "coding"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestActions_HandlePrompt_Bad(t *testing.T) { @@ -128,7 +128,7 @@ func TestActions_HandlePrompt_Bad(t *testing.T) { r := s.handlePrompt(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: "does-not-exist"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestActions_HandleTask_Good(t *testing.T) { @@ -136,7 +136,7 @@ func TestActions_HandleTask_Good(t *testing.T) { r := s.handleTask(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: "bug-fix"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestActions_HandleFlow_Good(t *testing.T) { @@ -144,7 +144,7 @@ func TestActions_HandleFlow_Good(t *testing.T) { r := s.handleFlow(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: "go"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestActions_HandlePersona_Good(t *testing.T) { @@ -157,14 +157,14 @@ func TestActions_HandlePersona_Good(t *testing.T) { r := s.handlePersona(context.Background(), core.NewOptions( core.Option{Key: "path", Value: personas[0]}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestActions_HandlePoke_Good(t *testing.T) { s := newPrepWithProcess() s.pokeCh = make(chan struct{}, 1) r := s.handlePoke(context.Background(), core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestActions_HandlePoke_Good_DelegatesToRunner(t *testing.T) { @@ -179,26 +179,26 @@ func TestActions_HandlePoke_Good_DelegatesToRunner(t *testing.T) { s.ServiceRuntime = core.NewServiceRuntime(c, AgentOptions{}) r := s.handlePoke(context.Background(), core.NewOptions()) - require.True(t, r.OK) - assert.True(t, called) + core.RequireTrue(t, r.OK) + core.AssertTrue(t, called) } func TestActions_HandleQA_Bad_NoWorkspace(t *testing.T) { s := newPrepWithProcess() r := s.handleQA(context.Background(), core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestActions_HandleVerify_Bad_NoWorkspace(t *testing.T) { s := newPrepWithProcess() r := s.handleVerify(context.Background(), core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestActions_HandleIngest_Bad_NoWorkspace(t *testing.T) { s := newPrepWithProcess() r := s.handleIngest(context.Background(), core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestActions_HandleComplete_Bad_NoCore(t *testing.T) { @@ -206,10 +206,10 @@ func TestActions_HandleComplete_Bad_NoCore(t *testing.T) { r := s.handleComplete(context.Background(), core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) err, ok := r.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "core runtime is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "core runtime is required") } func TestActions_HandleWorkspaceQuery_Good(t *testing.T) { @@ -218,19 +218,19 @@ func TestActions_HandleWorkspaceQuery_Good(t *testing.T) { s.workspaces.Set("core/go-io/task-42", &WorkspaceStatus{Status: "blocked", Repo: "go-io"}) r := s.handleWorkspaceQuery(nil, WorkspaceQuery{Status: "blocked"}) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) names, ok := r.Value.([]string) - require.True(t, ok) - require.Len(t, names, 1) - assert.Equal(t, "core/go-io/task-42", names[0]) + core.RequireTrue(t, ok) + core.AssertLen(t, names, 1) + core.AssertEqual(t, "core/go-io/task-42", names[0]) } func TestActions_HandleWorkspaceQuery_Bad(t *testing.T) { s := newPrepWithProcess() r := s.handleWorkspaceQuery(nil, "not-a-workspace-query") - assert.False(t, r.OK) - assert.Nil(t, r.Value) + core.AssertFalse(t, r.OK) + core.AssertNil(t, r.Value) } func TestActions_DispatchInputFromOptions_Good_MapsRFCFields(t *testing.T) { @@ -250,19 +250,19 @@ func TestActions_DispatchInputFromOptions_Good_MapsRFCFields(t *testing.T) { core.Option{Key: "dry-run", Value: "true"}, )) - assert.Equal(t, "go-io", input.Repo) - assert.Equal(t, "core", input.Org) - assert.Equal(t, "Fix the failing tests", input.Task) - assert.Equal(t, "codex:gpt-5.4", input.Agent) - assert.Equal(t, "coding", input.Template) - assert.Equal(t, "bug-fix", input.PlanTemplate) - assert.Equal(t, map[string]string{"ISSUE": "42", "MODE": "deep"}, input.Variables) - assert.Equal(t, "code/reviewer", input.Persona) - assert.Equal(t, 42, input.Issue) - assert.Equal(t, 7, input.PR) - assert.Equal(t, "agent/fix-tests", input.Branch) - assert.Equal(t, "v0.8.0", input.Tag) - assert.True(t, input.DryRun) + core.AssertEqual(t, "go-io", input.Repo) + core.AssertEqual(t, "core", input.Org) + core.AssertEqual(t, "Fix the failing tests", input.Task) + core.AssertEqual(t, "codex:gpt-5.4", input.Agent) + core.AssertEqual(t, "coding", input.Template) + core.AssertEqual(t, "bug-fix", input.PlanTemplate) + core.AssertEqual(t, map[string]string{"ISSUE": "42", "MODE": "deep"}, input.Variables) + core.AssertEqual(t, "code/reviewer", input.Persona) + core.AssertEqual(t, 42, input.Issue) + core.AssertEqual(t, 7, input.PR) + core.AssertEqual(t, "agent/fix-tests", input.Branch) + core.AssertEqual(t, "v0.8.0", input.Tag) + core.AssertTrue(t, input.DryRun) } func TestActions_PrepInputFromOptions_Good_MapsRFCFields(t *testing.T) { @@ -280,17 +280,17 @@ func TestActions_PrepInputFromOptions_Good_MapsRFCFields(t *testing.T) { core.Option{Key: "dry_run", Value: true}, )) - assert.Equal(t, "go-scm", input.Repo) - assert.Equal(t, "core", input.Org) - assert.Equal(t, "Prepare release branch", input.Task) - assert.Equal(t, "claude", input.Agent) - assert.Equal(t, 12, input.Issue) - assert.Equal(t, "dev", input.Branch) - assert.Equal(t, "security", input.Template) - assert.Equal(t, "release", input.PlanTemplate) - assert.Equal(t, map[string]string{"REPO": "go-scm", "MODE": "resume"}, input.Variables) - assert.Equal(t, "code/security", input.Persona) - assert.True(t, input.DryRun) + core.AssertEqual(t, "go-scm", input.Repo) + core.AssertEqual(t, "core", input.Org) + core.AssertEqual(t, "Prepare release branch", input.Task) + core.AssertEqual(t, "claude", input.Agent) + core.AssertEqual(t, 12, input.Issue) + core.AssertEqual(t, "dev", input.Branch) + core.AssertEqual(t, "security", input.Template) + core.AssertEqual(t, "release", input.PlanTemplate) + core.AssertEqual(t, map[string]string{"REPO": "go-scm", "MODE": "resume"}, input.Variables) + core.AssertEqual(t, "code/security", input.Persona) + core.AssertTrue(t, input.DryRun) } func TestActions_WatchInputFromOptions_Good_ParsesWorkspaceList(t *testing.T) { @@ -300,9 +300,9 @@ func TestActions_WatchInputFromOptions_Good_ParsesWorkspaceList(t *testing.T) { core.Option{Key: "timeout", Value: "900"}, )) - assert.Equal(t, []string{"core/go-io/task-5", "core/go-scm/task-6"}, input.Workspaces) - assert.Equal(t, 15, input.PollInterval) - assert.Equal(t, 900, input.Timeout) + core.AssertEqual(t, []string{"core/go-io/task-5", "core/go-scm/task-6"}, input.Workspaces) + core.AssertEqual(t, 15, input.PollInterval) + core.AssertEqual(t, 900, input.Timeout) } func TestActions_ReviewQueueInputFromOptions_Good_MapsLocalOnly(t *testing.T) { @@ -313,10 +313,10 @@ func TestActions_ReviewQueueInputFromOptions_Good_MapsLocalOnly(t *testing.T) { core.Option{Key: "local_only", Value: "yes"}, )) - assert.Equal(t, 4, input.Limit) - assert.Equal(t, "both", input.Reviewer) - assert.True(t, input.DryRun) - assert.True(t, input.LocalOnly) + core.AssertEqual(t, 4, input.Limit) + core.AssertEqual(t, "both", input.Reviewer) + core.AssertTrue(t, input.DryRun) + core.AssertTrue(t, input.LocalOnly) } func TestActions_EpicInputFromOptions_Good_ParsesListFields(t *testing.T) { @@ -332,15 +332,15 @@ func TestActions_EpicInputFromOptions_Good_ParsesListFields(t *testing.T) { core.Option{Key: "template", Value: "coding"}, )) - assert.Equal(t, "go-io", input.Repo) - assert.Equal(t, "core", input.Org) - assert.Equal(t, "AX RFC follow-up", input.Title) - assert.Equal(t, "Finish the remaining wrappers", input.Body) - assert.Equal(t, []string{"Map action inputs", "Add tests"}, input.Tasks) - assert.Equal(t, []string{"agentic", "ax"}, input.Labels) - assert.True(t, input.Dispatch) - assert.Equal(t, "codex", input.Agent) - assert.Equal(t, "coding", input.Template) + core.AssertEqual(t, "go-io", input.Repo) + core.AssertEqual(t, "core", input.Org) + core.AssertEqual(t, "AX RFC follow-up", input.Title) + core.AssertEqual(t, "Finish the remaining wrappers", input.Body) + core.AssertEqual(t, []string{"Map action inputs", "Add tests"}, input.Tasks) + core.AssertEqual(t, []string{"agentic", "ax"}, input.Labels) + core.AssertTrue(t, input.Dispatch) + core.AssertEqual(t, "codex", input.Agent) + core.AssertEqual(t, "coding", input.Template) } func TestActions_NormaliseForgeActionOptions_Good_MapsRepoAndNumber(t *testing.T) { @@ -350,9 +350,9 @@ func TestActions_NormaliseForgeActionOptions_Good_MapsRepoAndNumber(t *testing.T core.Option{Key: "title", Value: "Fix watcher"}, )) - assert.Equal(t, "go-io", options.String("_arg")) - assert.Equal(t, "12", options.String("number")) - assert.Equal(t, "Fix watcher", options.String("title")) + core.AssertEqual(t, "go-io", options.String("_arg")) + core.AssertEqual(t, "12", options.String("number")) + core.AssertEqual(t, "Fix watcher", options.String("title")) } func TestActions_OptionHelpers_Ugly_IgnoreMalformedMapJSON(t *testing.T) { @@ -362,5 +362,5 @@ func TestActions_OptionHelpers_Ugly_IgnoreMalformedMapJSON(t *testing.T) { core.Option{Key: "variables", Value: "{\"BROKEN\""}, )) - assert.Nil(t, input.Variables) + core.AssertNil(t, input.Variables) } diff --git a/pkg/agentic/auth.go b/pkg/agentic/auth.go index db6ee59e..977c3307 100644 --- a/pkg/agentic/auth.go +++ b/pkg/agentic/auth.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" ) // key := agentic.AgentApiKey{ID: 7, Name: "codex local", Prefix: "ak_abcd", IPRestrictions: []string{"10.0.0.0/8"}, RateLimit: 60} diff --git a/pkg/agentic/auth_test.go b/pkg/agentic/auth_test.go index 06b14fce..de296644 100644 --- a/pkg/agentic/auth_test.go +++ b/pkg/agentic/auth_test.go @@ -8,35 +8,33 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestAuth_HandleAuthProvision_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/agent/auth/provision", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) - require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/agent/auth/provision", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "Bearer secret-token", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "user-42", payload["oauth_user_id"]) - require.Equal(t, "codex local", payload["name"]) - require.Equal(t, float64(60), payload["rate_limit"]) - require.Equal(t, "2026-04-01T00:00:00Z", payload["expires_at"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "user-42", payload["oauth_user_id"]) + core.AssertEqual(t, "codex local", payload["name"]) + core.AssertEqual(t, float64(60), payload["rate_limit"]) + core.AssertEqual(t, "2026-04-01T00:00:00Z", payload["expires_at"]) permissions, ok := payload["permissions"].([]any) - require.True(t, ok) - require.Equal(t, []any{"plans:read", "plans:write"}, permissions) + core.RequireTrue(t, ok) + core.AssertEqual(t, []any{"plans:read", "plans:write"}, permissions) ipRestrictions, ok := payload["ip_restrictions"].([]any) - require.True(t, ok) - require.Equal(t, []any{"10.0.0.0/8", "192.168.0.0/16"}, ipRestrictions) + core.RequireTrue(t, ok) + core.AssertEqual(t, []any{"10.0.0.0/8", "192.168.0.0/16"}, ipRestrictions) _, _ = w.Write([]byte(`{"data":{"id":7,"workspace_id":3,"name":"codex local","key":"ak_live_secret","prefix":"ak_live","permissions":["plans:read","plans:write"],"ip_restrictions":["10.0.0.0/8","192.168.0.0/16"],"rate_limit":60,"call_count":2,"expires_at":"2026-04-01T00:00:00Z"}}`)) })) @@ -51,26 +49,26 @@ func TestAuth_HandleAuthProvision_Good(t *testing.T) { core.Option{Key: "rate_limit", Value: 60}, core.Option{Key: "expires_at", Value: "2026-04-01T00:00:00Z"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(AuthProvisionOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, 7, output.Key.ID) - assert.Equal(t, 3, output.Key.WorkspaceID) - assert.Equal(t, "codex local", output.Key.Name) - assert.Equal(t, "ak_live_secret", output.Key.Key) - assert.Equal(t, "ak_live", output.Key.Prefix) - assert.Equal(t, []string{"plans:read", "plans:write"}, output.Key.Permissions) - assert.Equal(t, []string{"10.0.0.0/8", "192.168.0.0/16"}, output.Key.IPRestrictions) - assert.Equal(t, 60, output.Key.RateLimit) - assert.Equal(t, 2, output.Key.CallCount) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, 7, output.Key.ID) + core.AssertEqual(t, 3, output.Key.WorkspaceID) + core.AssertEqual(t, "codex local", output.Key.Name) + core.AssertEqual(t, "ak_live_secret", output.Key.Key) + core.AssertEqual(t, "ak_live", output.Key.Prefix) + core.AssertEqual(t, []string{"plans:read", "plans:write"}, output.Key.Permissions) + core.AssertEqual(t, []string{"10.0.0.0/8", "192.168.0.0/16"}, output.Key.IPRestrictions) + core.AssertEqual(t, 60, output.Key.RateLimit) + core.AssertEqual(t, 2, output.Key.CallCount) } func TestAuth_HandleAuthProvision_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "secret-token") result := subsystem.handleAuthProvision(context.Background(), core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestAuth_HandleAuthProvision_Ugly(t *testing.T) { @@ -83,14 +81,14 @@ func TestAuth_HandleAuthProvision_Ugly(t *testing.T) { result := subsystem.handleAuthProvision(context.Background(), core.NewOptions( core.Option{Key: "oauth_user_id", Value: "user-42"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestAuth_HandleAuthRevoke_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/agent/auth/revoke/7", r.URL.Path) - require.Equal(t, http.MethodDelete, r.Method) - require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/agent/auth/revoke/7", r.URL.Path) + core.AssertEqual(t, http.MethodDelete, r.Method) + core.AssertEqual(t, "Bearer secret-token", r.Header.Get("Authorization")) _, _ = w.Write([]byte(`{"data":{"key_id":"7","revoked":true}}`)) })) defer server.Close() @@ -99,19 +97,19 @@ func TestAuth_HandleAuthRevoke_Good(t *testing.T) { result := subsystem.handleAuthRevoke(context.Background(), core.NewOptions( core.Option{Key: "key_id", Value: "7"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(AuthRevokeOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "7", output.KeyID) - assert.True(t, output.Revoked) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "7", output.KeyID) + core.AssertTrue(t, output.Revoked) } func TestAuth_HandleAuthRevoke_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "secret-token") result := subsystem.handleAuthRevoke(context.Background(), core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestAuth_HandleAuthRevoke_Ugly(t *testing.T) { @@ -124,29 +122,29 @@ func TestAuth_HandleAuthRevoke_Ugly(t *testing.T) { result := subsystem.handleAuthRevoke(context.Background(), core.NewOptions( core.Option{Key: "key_id", Value: "7"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(AuthRevokeOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "7", output.KeyID) - assert.True(t, output.Revoked) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "7", output.KeyID) + core.AssertTrue(t, output.Revoked) } func TestAuth_HandleAuthLogin_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/agent/auth/login", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/agent/auth/login", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) // Login is unauthenticated — pairing code is the proof. - require.Equal(t, "", r.Header.Get("Authorization")) + core.AssertEqual(t, "", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "123456", payload["code"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "123456", payload["code"]) _, _ = w.Write([]byte(`{"data":{"key":{"id":11,"name":"charon","key":"ak_live_abcdef","prefix":"ak_live","permissions":["fleet:run"],"expires_at":"2027-01-01T00:00:00Z"}}}`)) })) @@ -159,21 +157,21 @@ func TestAuth_HandleAuthLogin_Good(t *testing.T) { result := subsystem.handleAuthLogin(context.Background(), core.NewOptions( core.Option{Key: "code", Value: "123456"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(AuthLoginOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, 11, output.Key.ID) - assert.Equal(t, "ak_live_abcdef", output.Key.Key) - assert.Equal(t, "ak_live", output.Key.Prefix) - assert.Equal(t, []string{"fleet:run"}, output.Key.Permissions) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, 11, output.Key.ID) + core.AssertEqual(t, "ak_live_abcdef", output.Key.Key) + core.AssertEqual(t, "ak_live", output.Key.Prefix) + core.AssertEqual(t, []string{"fleet:run"}, output.Key.Permissions) } func TestAuth_HandleAuthLogin_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "") result := subsystem.handleAuthLogin(context.Background(), core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestAuth_HandleAuthLogin_Ugly(t *testing.T) { @@ -190,5 +188,5 @@ func TestAuth_HandleAuthLogin_Ugly(t *testing.T) { result := subsystem.handleAuthLogin(context.Background(), core.NewOptions( core.Option{Key: "code", Value: "999999"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } diff --git a/pkg/agentic/auto_pr.go b/pkg/agentic/auto_pr.go index cd8da710..5fbd4fd4 100644 --- a/pkg/agentic/auto_pr.go +++ b/pkg/agentic/auto_pr.go @@ -6,8 +6,8 @@ import ( "context" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" ) // s.autoCreatePR("/srv/.core/workspace/core/go-io/task-5") diff --git a/pkg/agentic/auto_pr_example_test.go b/pkg/agentic/auto_pr_example_test.go index a2bff7c9..63a5afd2 100644 --- a/pkg/agentic/auto_pr_example_test.go +++ b/pkg/agentic/auto_pr_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func Example_truncate() { core.Println(truncate("hello world", 5)) diff --git a/pkg/agentic/auto_pr_test.go b/pkg/agentic/auto_pr_test.go index ce0c0509..5cdf5832 100644 --- a/pkg/agentic/auto_pr_test.go +++ b/pkg/agentic/auto_pr_test.go @@ -4,16 +4,99 @@ package agentic import ( "context" + "net/http" + "net/http/httptest" "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" + "dappco.re/go/forge" ) func TestAutopr_AutoCreatePR_Good(t *testing.T) { - t.Skip("needs real git + forge integration") + root := t.TempDir() + setTestWorkspace(t, root) + + remoteDir := core.JoinPath(root, "remote.git") + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "init", "--bare", remoteDir).OK) + + seedDir := core.JoinPath(root, "seed") + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "clone", remoteDir, seedDir).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), seedDir, "git", "config", "user.name", "Test").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), seedDir, "git", "config", "user.email", "test@example.com").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), seedDir, "git", "checkout", "-b", "dev").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(seedDir, "README.md"), "# seed\n").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), seedDir, "git", "add", ".").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), seedDir, "git", "commit", "-m", "seed").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), seedDir, "git", "push", "-u", "origin", "dev").OK) + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "--git-dir", remoteDir, "symbolic-ref", "HEAD", "refs/heads/dev").OK) + + workspaceDir := core.JoinPath(root, "workspace", "core", "go-io", "task-5") + repoDir := WorkspaceRepoDir(workspaceDir) + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "clone", remoteDir, repoDir).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@example.com").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "checkout", "-b", "agent/fix-tests", "origin/dev").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(repoDir, "README.md"), "# seed\nfeature change\n").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", "README.md").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "feature").OK) + + gitConfigPath := core.JoinPath(root, "gitconfig") + gitConfig := core.Concat( + "[url \"file://", remoteDir, "\"]\n", + "\tinsteadOf = ssh://git@forge.lthn.ai:2223/core/go-io.git\n", + ) + core.RequireTrue(t, fs.Write(gitConfigPath, gitConfig).OK) + t.Setenv("GIT_CONFIG_GLOBAL", gitConfigPath) + + var requestBody string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/api/v1/repos/core/go-io/pulls", r.URL.Path) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + requestBody = bodyResult.Value.(string) + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"number":17,"html_url":"https://forge.test/core/go-io/pulls/17"}`)) + })) + defer server.Close() + + status := &WorkspaceStatus{ + Status: "completed", + Agent: "codex", + Repo: "go-io", + Org: "core", + Task: "Fix tests", + Branch: "agent/fix-tests", + Issue: 42, + StartedAt: time.Now(), + UpdatedAt: time.Now(), + } + core.RequireNoError(t, writeStatus(workspaceDir, status)) + + s := &PrepSubsystem{ + ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), + forge: forge.NewForge(server.URL, "test-token"), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + s.autoCreatePR(workspaceDir) + + statusResult := ReadStatusResult(workspaceDir) + updated, ok := workspaceStatusValue(statusResult) + core.RequireTrue(t, ok) + core.AssertEqual(t, "https://forge.test/core/go-io/pulls/17", updated.PRURL) + core.AssertContains(t, requestBody, `"head":"agent/fix-tests"`) + core.AssertContains(t, requestBody, `"base":"dev"`) + core.AssertContains(t, requestBody, `"title":"[agent/codex] Fix tests"`) + + remoteHeads := testCore.Process().RunIn(context.Background(), repoDir, "git", "ls-remote", "--heads", remoteDir, "agent/fix-tests") + core.RequireTrue(t, remoteHeads.OK) + core.AssertNotContains(t, remoteHeads.Value.(string), "agent/fix-tests") } func TestAutopr_AutoCreatePR_Bad(t *testing.T) { @@ -29,7 +112,7 @@ func TestAutopr_AutoCreatePR_Bad(t *testing.T) { // No status file → early return (no panic) wsNoStatus := core.JoinPath(root, "ws-no-status") fs.EnsureDir(wsNoStatus) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.autoCreatePR(wsNoStatus) }) @@ -39,7 +122,7 @@ func TestAutopr_AutoCreatePR_Bad(t *testing.T) { fs.Write(core.JoinPath(wsNoBranch, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ Status: "completed", Agent: "codex", Repo: "go-io", Branch: "", })) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.autoCreatePR(wsNoBranch) }) @@ -49,7 +132,7 @@ func TestAutopr_AutoCreatePR_Bad(t *testing.T) { fs.Write(core.JoinPath(wsNoRepo, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ Status: "completed", Agent: "codex", Repo: "", Branch: "agent/fix-tests", })) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.autoCreatePR(wsNoRepo) }) } @@ -89,31 +172,31 @@ func TestAutopr_AutoCreatePR_Ugly(t *testing.T) { } // git log origin/dev..HEAD will fail (no origin remote) → early return - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.autoCreatePR(wsDir) }) } func TestAutopr_CleanupForgeBranch_Good_DeletesRemoteBranch(t *testing.T) { remoteDir := core.JoinPath(t.TempDir(), "remote.git") - require.True(t, testCore.Process().Run(context.Background(), "git", "init", "--bare", remoteDir).OK) + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "init", "--bare", remoteDir).OK) repoDir := core.JoinPath(t.TempDir(), "repo") - require.True(t, testCore.Process().Run(context.Background(), "git", "clone", remoteDir, repoDir).OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@example.com").OK) + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "clone", remoteDir, repoDir).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@example.com").OK) branch := "agent/fix-branch" - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "checkout", "-b", branch).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "checkout", "-b", branch).OK) fs.Write(core.JoinPath(repoDir, "README.md"), "# test") - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "init").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "push", "-u", "origin", branch).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "init").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "push", "-u", "origin", branch).OK) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{})} - assert.True(t, s.cleanupForgeBranch(context.Background(), repoDir, remoteDir, branch)) + core.AssertTrue(t, s.cleanupForgeBranch(context.Background(), repoDir, remoteDir, branch)) remoteHeads := testCore.Process().RunIn(context.Background(), repoDir, "git", "ls-remote", "--heads", remoteDir, branch) - require.True(t, remoteHeads.OK) - assert.NotContains(t, remoteHeads.Value.(string), branch) + core.RequireTrue(t, remoteHeads.OK) + core.AssertNotContains(t, remoteHeads.Value.(string), branch) } diff --git a/pkg/agentic/ax7_gap_closers_test.go b/pkg/agentic/ax7_gap_closers_test.go new file mode 100644 index 00000000..fbe78f39 --- /dev/null +++ b/pkg/agentic/ax7_gap_closers_test.go @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + core "dappco.re/go" + "github.com/modelcontextprotocol/go-sdk/mcp" + "gopkg.in/yaml.v3" +) + +func writeFakeAgentBinary(t *testing.T, name string) { + t.Helper() + + binDir := t.TempDir() + binaryPath := core.JoinPath(binDir, name) + script := "#!/bin/sh\nprintf 'done'\n" + core.RequireTrue(t, fs.Write(binaryPath, script).OK) + + chmodResult := testCore.Process().Run(context.Background(), "chmod", "+x", binaryPath) + core.RequireTrue(t, chmodResult.OK) + t.Setenv("PATH", core.Concat(binDir, ":", core.Env("PATH"))) +} + +func TestCallBody_RemoteClient_ToolCallBody_Bad(t *testing.T) { + client := NewRemoteClient("local") + body := client.ToolCallBody(-1, "agentic_dispatch", map[string]any{}) + + var payload map[string]any + result := core.JSONUnmarshal(body, &payload) + core.RequireTrue(t, result.OK) + + params, ok := payload["params"].(map[string]any) + core.RequireTrue(t, ok) + arguments, ok := params["arguments"].(map[string]any) + core.RequireTrue(t, ok) + core.AssertEqual(t, float64(-1), payload["id"]) + core.AssertLen(t, arguments, 0) +} + +func TestDuplicateRegistration_RegisterHTTPTransport_Bad(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, "POST", r.Method) + core.AssertEmpty(t, r.Header.Get("Authorization")) + _, _ = w.Write([]byte(`{"status":"ok"}`)) + })) + t.Cleanup(srv.Close) + + c := core.New() + RegisterHTTPTransport(c) + RegisterHTTPTransport(c) + c.Drive().New(core.NewOptions( + core.Option{Key: "name", Value: "remote"}, + core.Option{Key: "transport", Value: srv.URL}, + )) + + streamResult := c.API().Stream("remote") + core.RequireTrue(t, streamResult.OK) + + stream := streamResult.Value.(core.Stream) + sendErr := stream.Send([]byte(`{"ping":1}`)) + core.RequireNoError(t, sendErr) + + response, receiveErr := stream.Receive() + core.RequireNoError(t, receiveErr) + core.AssertEqual(t, `{"status":"ok"}`, string(response)) +} + +func TestTransport_HTTPPost_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "Bearer post-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"title":"Fix tests"}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"created":true}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPPost(context.Background(), srv.URL, `{"title":"Fix tests"}`, "post-token", "Bearer") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, `{"created":true}`, result.Value.(string)) +} + +func TestTransport_HTTPPatch_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, http.MethodPatch, r.Method) + core.AssertEqual(t, "token patch-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"status":"done"}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"updated":true}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPPatch(context.Background(), srv.URL, `{"status":"done"}`, "patch-token", "token") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, `{"updated":true}`, result.Value.(string)) +} + +func TestTransport_HTTPDelete_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, http.MethodDelete, r.Method) + core.AssertEqual(t, "Bearer delete-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"reason":"stale"}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"deleted":true}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPDelete(context.Background(), srv.URL, `{"reason":"stale"}`, "delete-token", "Bearer") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, `{"deleted":true}`, result.Value.(string)) +} + +func TestTransport_HTTPDo_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, http.MethodPut, r.Method) + core.AssertEqual(t, "token do-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"value":7}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"ok":true}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPDo(context.Background(), http.MethodPut, srv.URL, `{"value":7}`, "do-token", "token") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, `{"ok":true}`, result.Value.(string)) +} + +func TestTransport_DrivePost_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, "/issues", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "Bearer drive-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"title":"Follow up"}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"number":9}`)) + })) + t.Cleanup(srv.Close) + + c := core.New() + c.Drive().New(core.NewOptions( + core.Option{Key: "name", Value: "forge"}, + core.Option{Key: "transport", Value: srv.URL}, + core.Option{Key: "token", Value: "drive-token"}, + )) + + result := DrivePost(c, "forge", "/issues", `{"title":"Follow up"}`, "Bearer") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, `{"number":9}`, result.Value.(string)) +} + +func TestTransport_DriveDo_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, "/pulls/3", r.URL.Path) + core.AssertEqual(t, http.MethodPatch, r.Method) + core.AssertEqual(t, "token drive-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"state":"closed"}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"closed":true}`)) + })) + t.Cleanup(srv.Close) + + c := core.New() + c.Drive().New(core.NewOptions( + core.Option{Key: "name", Value: "forge"}, + core.Option{Key: "transport", Value: srv.URL}, + core.Option{Key: "token", Value: "drive-token"}, + )) + + result := DriveDo(c, "forge", http.MethodPatch, "/pulls/3", `{"state":"closed"}`, "token") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, `{"closed":true}`, result.Value.(string)) +} + +func TestMissingDrive_DriveGet_Bad(t *testing.T) { + c := core.New() + result := DriveGet(c, "missing", "/repos/core/go-io", "Bearer") + + core.AssertFalse(t, result.OK) + err, ok := result.Value.(error) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "drive not found") +} + +func TestTransport_Stream_Send_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "token send-token", r.Header.Get("Authorization")) + + bodyResult := core.ReadAll(r.Body) + core.RequireTrue(t, bodyResult.OK) + core.AssertEqual(t, `{"ping":1}`, bodyResult.Value.(string)) + _, _ = w.Write([]byte(`{"pong":1}`)) + })) + t.Cleanup(srv.Close) + + stream := &httpStream{client: defaultClient, url: srv.URL, token: "send-token", method: http.MethodPost} + sendErr := stream.Send([]byte(`{"ping":1}`)) + core.RequireNoError(t, sendErr) + core.AssertEqual(t, `{"pong":1}`, string(stream.response)) +} + +func TestTransport_Stream_Receive_Good(t *testing.T) { + stream := &httpStream{response: []byte(`{"cached":true}`)} + response, err := stream.Receive() + + core.RequireNoError(t, err) + core.AssertEqual(t, `{"cached":true}`, string(response)) +} + +func TestTransport_Stream_Close_Good(t *testing.T) { + stream := &httpStream{} + err := stream.Close() + + core.RequireNoError(t, err) + core.AssertNil(t, stream.response) +} + +func TestClock_SyncRealClock_Now_Good(t *testing.T) { + clock := remoteSyncRealClock{} + before := time.Now() + now := clock.Now() + after := time.Now() + + core.AssertFalse(t, now.Before(before)) + core.AssertFalse(t, now.After(after)) +} + +func TestClock_SyncRealClock_After_Good(t *testing.T) { + clock := remoteSyncRealClock{} + start := time.Now() + ch := clock.After(time.Millisecond) + + select { + case firedAt := <-ch: + core.AssertFalse(t, firedAt.Before(start)) + case <-time.After(time.Second): + t.Fatal("expected remoteSyncRealClock.After to fire") + } +} + +func TestCluster_ClusterUnion_Find_Good(t *testing.T) { + union := newQAClusterUnion(3) + union.parent[1] = 0 + root := union.Find(1) + + core.AssertEqual(t, 0, root) + core.AssertEqual(t, 0, union.parent[1]) +} + +func TestCluster_ClusterUnion_Union_Good(t *testing.T) { + union := newQAClusterUnion(4) + union.Union(0, 1) + union.Union(1, 2) + + left := union.Find(0) + right := union.Find(2) + core.AssertEqual(t, left, right) + core.AssertEqual(t, 3, union.size[left]) +} + +func TestYAML_ConcurrencyLimit_UnmarshalYAML_Good(t *testing.T) { + var limit ConcurrencyLimit + err := yaml.Unmarshal([]byte("total: 3\ngpt-5.4: 2\ngpt-5.3-codex-spark: 1\n"), &limit) + + core.RequireNoError(t, err) + core.AssertEqual(t, 3, limit.Total) + core.AssertEqual(t, 2, limit.Models["gpt-5.4"]) + core.AssertEqual(t, 1, limit.Models["gpt-5.3-codex-spark"]) +} + +func TestRegistry_PrepSubsystem_Workspaces_Good(t *testing.T) { + registry := core.NewRegistry[*WorkspaceStatus]() + registry.Set("core/go-io/task-5", &WorkspaceStatus{Status: "queued"}) + + subsystem := &PrepSubsystem{workspaces: registry} + result := subsystem.Workspaces().Get("core/go-io/task-5") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, "queued", result.Value.(*WorkspaceStatus).Status) +} + +func TestMirrorsQueueState_PrepSubsystem_TrackWorkspace_Good(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{workspaces: core.NewRegistry[*WorkspaceStatus]()} + defer subsystem.closeStateStore() + + status := &WorkspaceStatus{ + Status: "queued", + Agent: "codex:gpt-5.4", + Repo: "go-io", + Branch: "agent/fix-tests", + StartedAt: time.Now(), + } + subsystem.TrackWorkspace("core/go-io/task-5", status) + + registryResult := subsystem.Workspaces().Get("core/go-io/task-5") + core.RequireTrue(t, registryResult.OK) + core.AssertEqual(t, "queued", registryResult.Value.(*WorkspaceStatus).Status) + core.AssertEqual(t, 1, subsystem.stateStoreCount(stateQueueGroup)) +} + +func TestDispatchSync_PrepSubsystem_DispatchSync_Good(t *testing.T) { + dir := t.TempDir() + setTestWorkspace(t, dir) + + workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-9") + subsystem := &PrepSubsystem{dispatchSyncTick: 10 * time.Millisecond} + + subsystem.dispatchSyncPrep = func(_ context.Context, _ *mcp.CallToolRequest, input PrepInput) (*mcp.CallToolResult, PrepOutput, error) { + core.AssertEqual(t, "core", input.Org) + core.AssertEqual(t, "go-io", input.Repo) + core.AssertEqual(t, "codex", input.Agent) + core.AssertEqual(t, "Fix tests", input.Task) + core.AssertEqual(t, 9, input.Issue) + + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ + Status: "completed", + PRURL: "https://forge.test/core/go-io/pulls/9", + })).OK) + + return nil, PrepOutput{ + Success: true, + WorkspaceDir: workspaceDir, + Branch: "agent/fix-tests", + Prompt: "prompt", + }, nil + } + subsystem.dispatchSyncSpawn = func(agent, prompt, workspaceDir string) (int, string, string, error) { + core.AssertEqual(t, "codex", agent) + core.AssertEqual(t, "prompt", prompt) + core.AssertContains(t, workspaceDir, "task-9") + return 42, "process-42", core.JoinPath(workspaceDir, ".meta", "agent.log"), nil + } + + result := subsystem.DispatchSync(context.Background(), DispatchSyncInput{ + Org: "core", + Repo: "go-io", + Agent: "codex", + Task: "Fix tests", + Issue: 9, + }) + + core.AssertTrue(t, result.OK) + core.AssertEqual(t, "completed", result.Status) + core.AssertEqual(t, "https://forge.test/core/go-io/pulls/9", result.PRURL) +} + +func TestQueuedAgent_PrepSubsystem_SpawnFromQueue_Good(t *testing.T) { + root := t.TempDir() + setTestWorkspace(t, root) + writeFakeAgentBinary(t, "claude") + + workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-3") + core.RequireTrue(t, fs.EnsureDir(WorkspaceRepoDir(workspaceDir)).OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceMetaDir(workspaceDir)).OK) + + subsystem := newPrepWithProcess() + result := subsystem.SpawnFromQueue("claude", "Write docs", workspaceDir) + core.RequireTrue(t, result.OK) + + pid, ok := result.Value.(int) + core.RequireTrue(t, ok) + if pid <= 0 { + t.Fatalf("expected positive pid, got %d", pid) + } + + outputPath := agentOutputFile(workspaceDir, "claude") + requireEventually(t, func() bool { return fs.IsFile(outputPath) }, 5*time.Second, 10*time.Millisecond) + outputResult := fs.Read(outputPath) + core.RequireTrue(t, outputResult.OK) + output := core.Trim(outputResult.Value.(string)) + core.AssertEqual(t, "done", output) +} + +func TestMultipleRevisions_PrepSubsystem_ScheduleRevision_Ugly(t *testing.T) { + withStateStoreTempDir(t) + + firstTime := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) + restoreContentSEONow(t, firstTime) + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + first, firstErr := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "First copy") + core.RequireNoError(t, firstErr) + + secondTime := firstTime.Add(time.Minute) + restoreContentSEONow(t, secondTime) + second, secondErr := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Second copy") + core.RequireNoError(t, secondErr) + + pending, pendingErr := subsystem.GetPendingRevisions("/help/hosting") + core.RequireNoError(t, pendingErr) + core.AssertLen(t, pending, 2) + core.AssertNotEqual(t, first.CreatedAt, second.CreatedAt) + core.AssertContains(t, []string{pending[0].Content, pending[1].Content}, "First copy") + core.AssertContains(t, []string{pending[0].Content, pending[1].Content}, "Second copy") +} + +func TestMetaReader_ForgeMetaReader_GetIssueState_Good(t *testing.T) { + repo := newPipelineTestRepo() + repo.Issues[7] = &pipelineTestIssue{ + Number: 7, + Title: "Fix the flaky tests", + Body: "Body", + State: "closed", + Labels: []string{"bug", "agentic"}, + } + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": repo}) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + state, err := reader.GetIssueState(context.Background(), "go-io", 7) + + core.RequireNoError(t, err) + core.AssertEqual(t, 7, state.Number) + core.AssertEqual(t, "closed", state.State) + core.AssertEqual(t, "Fix the flaky tests", state.Title) + core.AssertContains(t, state.Labels, "bug") +} + +func TestMetaReader_ForgeMetaReader_GetPRMeta_Good(t *testing.T) { + repo := newPipelineTestRepo() + repo.Pulls[12] = &pipelineTestPR{ + Number: 12, + Title: "Stabilise pipeline", + State: "open", + Mergeable: boolPtr(true), + MergeableState: "clean", + HeadRef: "agent/stabilise-pipeline", + HeadSHA: "sha-12", + BaseRef: "dev", + ReviewThreadsTotal: 3, + ReviewThreadsResolved: 2, + Statuses: []map[string]any{ + {"context": "qa", "status": "success"}, + {"name": "build", "conclusion": "failure"}, + }, + Reactions: map[string]int{"eyes": 1}, + } + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": repo}) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + meta, err := reader.GetPRMeta(context.Background(), "go-io", 12) + + core.RequireNoError(t, err) + core.AssertEqual(t, 12, meta.Number) + core.AssertEqual(t, "mergeable", meta.Mergeable) + core.AssertEqual(t, "agent/stabilise-pipeline", meta.HeadBranch) + core.AssertLen(t, meta.Checks, 2) + core.AssertEqual(t, 3, meta.ThreadsTotal) + core.AssertEqual(t, 2, meta.ThreadsResolved) + core.AssertTrue(t, meta.HasEyesReaction) +} + +func TestMetaReader_ForgeMetaReader_GetEpicMeta_Good(t *testing.T) { + repo := newPipelineTestRepo() + repo.Issues[1] = &pipelineTestIssue{ + Number: 1, + Title: "Epic", + State: "open", + Body: "Epic branch: `agent/epic`\n- [x] #2 Done child\n- [ ] #3 Open child", + } + repo.Issues[2] = &pipelineTestIssue{Number: 2, Title: "Done child", State: "closed"} + repo.Issues[3] = &pipelineTestIssue{Number: 3, Title: "Open child", State: "open"} + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": repo}) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + meta, err := reader.GetEpicMeta(context.Background(), "go-io", 1) + + core.RequireNoError(t, err) + core.AssertEqual(t, 1, meta.Number) + core.AssertEqual(t, "agent/epic", meta.Branch) + core.AssertLen(t, meta.Children, 2) + core.AssertTrue(t, meta.Children[0].Checked) + core.AssertEqual(t, "closed", meta.Children[0].State) + core.AssertEqual(t, "open", meta.Children[1].State) +} + +func TestMetaReader_ForgeMetaReader_GetCommentReactions_Good(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + core.AssertEqual(t, "/api/v1/repos/core/go-io/issues/comments/55/reactions", r.URL.Path) + core.AssertEqual(t, "token test-token", r.Header.Get("Authorization")) + _, _ = w.Write([]byte(`[{"content":"eyes"},{"content":"eyes"},{"content":"rocket"}]`)) + })) + t.Cleanup(srv.Close) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + reactions, err := reader.GetCommentReactions(context.Background(), "go-io", 55) + + core.RequireNoError(t, err) + core.AssertLen(t, reactions, 2) + + counts := map[string]int{} + for _, reaction := range reactions { + counts[reaction.Content] = reaction.Count + } + core.AssertEqual(t, 2, counts["eyes"]) + core.AssertEqual(t, 1, counts["rocket"]) +} diff --git a/pkg/agentic/ax7_gap_variants_test.go b/pkg/agentic/ax7_gap_variants_test.go new file mode 100644 index 00000000..3b3adc31 --- /dev/null +++ b/pkg/agentic/ax7_gap_variants_test.go @@ -0,0 +1,673 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + core "dappco.re/go" + "dappco.re/go/agent/pkg/messages" + coremcp "dappco.re/go/mcp/pkg/mcp" + mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" + "gopkg.in/yaml.v3" +) + +func registerToolNames(t *testing.T, svc *coremcp.Service) []string { + t.Helper() + + server := svc.Server() + client := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "test", Version: "0.1.0"}, nil) + clientTransport, serverTransport := mcpsdk.NewInMemoryTransports() + + serverSession, err := server.Connect(context.Background(), serverTransport, nil) + core.RequireNoError(t, err) + t.Cleanup(func() { _ = serverSession.Close() }) + + clientSession, err := client.Connect(context.Background(), clientTransport, nil) + core.RequireNoError(t, err) + t.Cleanup(func() { _ = clientSession.Close() }) + + result, err := clientSession.ListTools(context.Background(), nil) + core.RequireNoError(t, err) + + names := make([]string, 0, len(result.Tools)) + for _, tool := range result.Tools { + names = append(names, tool.Name) + } + return names +} + +func TestServerError_HTTPGet_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadGateway) + _, _ = w.Write([]byte(`{"error":"get upstream"}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPGet(context.Background(), srv.URL, "", "") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"get upstream"}`, result.Value.(string)) +} + +func TestInvalidURL_HTTPPost_Bad(t *testing.T) { + result := HTTPPost(context.Background(), "://bad", `{"title":"Fix tests"}`, "", "") + core.AssertFalse(t, result.OK) + + err, ok := result.Value.(error) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "create request") +} + +func TestServerError_HTTPPost_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadGateway) + _, _ = w.Write([]byte(`{"error":"post upstream"}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPPost(context.Background(), srv.URL, `{"title":"Fix tests"}`, "", "") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"post upstream"}`, result.Value.(string)) +} + +func TestInvalidURL_HTTPPatch_Bad(t *testing.T) { + result := HTTPPatch(context.Background(), "://bad", `{"status":"done"}`, "", "") + core.AssertFalse(t, result.OK) + + err, ok := result.Value.(error) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "create request") +} + +func TestServerError_HTTPPatch_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + _, _ = w.Write([]byte(`{"error":"patch upstream"}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPPatch(context.Background(), srv.URL, `{"status":"done"}`, "", "") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"patch upstream"}`, result.Value.(string)) +} + +func TestInvalidURL_HTTPDelete_Bad(t *testing.T) { + result := HTTPDelete(context.Background(), "://bad", `{"reason":"stale"}`, "", "") + core.AssertFalse(t, result.OK) + + err, ok := result.Value.(error) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "create request") +} + +func TestInvalidURL_HTTPDo_Bad(t *testing.T) { + result := HTTPDo(context.Background(), http.MethodPut, "://bad", `{"value":7}`, "", "") + core.AssertFalse(t, result.OK) + + err, ok := result.Value.(error) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "create request") +} + +func TestServerError_HTTPDo_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusGatewayTimeout) + _, _ = w.Write([]byte(`{"error":"put upstream"}`)) + })) + t.Cleanup(srv.Close) + + result := HTTPDo(context.Background(), http.MethodPut, srv.URL, `{"value":7}`, "", "") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"put upstream"}`, result.Value.(string)) +} + +func TestServerError_DriveGet_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + _, _ = w.Write([]byte(`{"error":"drive get upstream"}`)) + })) + t.Cleanup(srv.Close) + + c := core.New() + c.Drive().New(core.NewOptions( + core.Option{Key: "name", Value: "forge"}, + core.Option{Key: "transport", Value: srv.URL}, + core.Option{Key: "token", Value: "drive-token"}, + )) + + result := DriveGet(c, "forge", "/repos/core/go-io", "Bearer") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"drive get upstream"}`, result.Value.(string)) +} + +func TestMissingDrive_DrivePost_Bad(t *testing.T) { + result := DrivePost(core.New(), "missing", "/issues", `{"title":"Follow up"}`, "Bearer") + core.AssertFalse(t, result.OK) + + err, ok := result.Value.(error) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "drive not found") +} + +func TestServerError_DrivePost_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadGateway) + _, _ = w.Write([]byte(`{"error":"drive post upstream"}`)) + })) + t.Cleanup(srv.Close) + + c := core.New() + c.Drive().New(core.NewOptions( + core.Option{Key: "name", Value: "forge"}, + core.Option{Key: "transport", Value: srv.URL}, + core.Option{Key: "token", Value: "drive-token"}, + )) + + result := DrivePost(c, "forge", "/issues", `{"title":"Follow up"}`, "Bearer") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"drive post upstream"}`, result.Value.(string)) +} + +func TestServerError_DriveDo_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"error":"drive do upstream"}`)) + })) + t.Cleanup(srv.Close) + + c := core.New() + c.Drive().New(core.NewOptions( + core.Option{Key: "name", Value: "forge"}, + core.Option{Key: "transport", Value: srv.URL}, + core.Option{Key: "token", Value: "drive-token"}, + )) + + result := DriveDo(c, "forge", http.MethodPatch, "/pulls/3", `{"state":"closed"}`, "token") + core.AssertFalse(t, result.OK) + core.AssertEqual(t, `{"error":"drive do upstream"}`, result.Value.(string)) +} + +func TestInvalidURL_Stream_Send_Bad(t *testing.T) { + stream := &httpStream{client: defaultClient, url: "://bad", method: http.MethodPost} + sendErr := stream.Send([]byte(`{"ping":1}`)) + + core.AssertError(t, sendErr) + core.AssertContains(t, sendErr.Error(), "missing protocol scheme") +} + +func TestNilClient_Stream_Send_Ugly(t *testing.T) { + stream := &httpStream{url: "http://example.com", method: http.MethodPost} + core.AssertPanics(t, func() { + _ = stream.Send([]byte(`{"ping":1}`)) + }) +} + +func TestEmptyBuffer_Stream_Receive_Bad(t *testing.T) { + stream := &httpStream{} + response, err := stream.Receive() + + core.RequireNoError(t, err) + core.AssertNil(t, response) +} + +func TestNilReceiver_Stream_Receive_Ugly(t *testing.T) { + var stream *httpStream + core.AssertPanics(t, func() { + _, _ = stream.Receive() + }) +} + +func TestZeroValue_Stream_Close_Bad(t *testing.T) { + stream := &httpStream{} + err := stream.Close() + + core.RequireNoError(t, err) + core.AssertNil(t, stream.client) +} + +func TestNilReceiver_Stream_Close_Ugly(t *testing.T) { + var stream *httpStream + err := stream.Close() + core.AssertNoError(t, err) + core.AssertNil(t, stream) +} + +func TestNonZero_SyncRealClock_Now_Bad(t *testing.T) { + clock := remoteSyncRealClock{} + now := clock.Now() + + core.AssertFalse(t, now.IsZero()) + core.AssertEqual(t, now.Location(), time.Now().Location()) +} + +func TestOrderedCalls_SyncRealClock_Now_Ugly(t *testing.T) { + clock := remoteSyncRealClock{} + first := clock.Now() + second := clock.Now() + + core.AssertFalse(t, second.Before(first)) + core.AssertFalse(t, first.IsZero()) +} + +func TestImmediate_SyncRealClock_After_Bad(t *testing.T) { + clock := remoteSyncRealClock{} + ch := clock.After(0) + + select { + case <-ch: + case <-time.After(time.Second): + t.Fatal("expected immediate After(0) notification") + } +} + +func TestNegativeDelay_SyncRealClock_After_Ugly(t *testing.T) { + clock := remoteSyncRealClock{} + ch := clock.After(-time.Millisecond) + + select { + case <-ch: + case <-time.After(time.Second): + t.Fatal("expected After on negative delay to fire") + } +} + +func TestRoot_ClusterUnion_Find_Bad(t *testing.T) { + union := newQAClusterUnion(2) + root := union.Find(0) + + core.AssertEqual(t, 0, root) + core.AssertEqual(t, 0, union.parent[0]) +} + +func TestCompression_ClusterUnion_Find_Ugly(t *testing.T) { + union := newQAClusterUnion(4) + union.parent[3] = 2 + union.parent[2] = 1 + union.parent[1] = 0 + root := union.Find(3) + + core.AssertEqual(t, 0, root) + core.AssertEqual(t, 0, union.parent[3]) +} + +func TestDuplicate_ClusterUnion_Union_Bad(t *testing.T) { + union := newQAClusterUnion(2) + union.Union(0, 0) + + core.AssertEqual(t, 0, union.Find(0)) + core.AssertEqual(t, 1, union.size[0]) +} + +func TestConnected_ClusterUnion_Union_Ugly(t *testing.T) { + union := newQAClusterUnion(3) + union.Union(0, 1) + union.Union(1, 2) + union.Union(0, 2) + + root := union.Find(0) + core.AssertEqual(t, root, union.Find(2)) + core.AssertEqual(t, 3, union.size[root]) +} + +func TestScalar_ConcurrencyLimit_UnmarshalYAML_Bad(t *testing.T) { + var limit ConcurrencyLimit + err := yaml.Unmarshal([]byte("2\n"), &limit) + + core.RequireNoError(t, err) + core.AssertEqual(t, 2, limit.Total) + core.AssertNil(t, limit.Models) +} + +func TestInvalid_ConcurrencyLimit_UnmarshalYAML_Ugly(t *testing.T) { + var limit ConcurrencyLimit + err := yaml.Unmarshal([]byte("total: nope\n"), &limit) + + core.AssertError(t, err) + core.AssertEqual(t, 0, limit.Total) +} + +func TestNilRegistry_PrepSubsystem_Workspaces_Bad(t *testing.T) { + subsystem := &PrepSubsystem{} + workspaces := subsystem.Workspaces() + + core.AssertNil(t, workspaces) + core.AssertEqual(t, (*core.Registry[*WorkspaceStatus])(nil), workspaces) +} + +func TestLiveRegistry_PrepSubsystem_Workspaces_Ugly(t *testing.T) { + subsystem := &PrepSubsystem{workspaces: core.NewRegistry[*WorkspaceStatus]()} + workspaces := subsystem.Workspaces() + workspaces.Set("core/go-store/task-2", &WorkspaceStatus{Status: "running"}) + + result := subsystem.workspaces.Get("core/go-store/task-2") + core.RequireTrue(t, result.OK) + core.AssertEqual(t, "running", result.Value.(*WorkspaceStatus).Status) +} + +func TestNilStatus_PrepSubsystem_TrackWorkspace_Bad(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{workspaces: core.NewRegistry[*WorkspaceStatus]()} + defer subsystem.closeStateStore() + subsystem.TrackWorkspace("core/go-io/task-5", &WorkspaceStatus{Status: "queued", Agent: "codex", Repo: "go-io", Branch: "agent/fix"}) + subsystem.TrackWorkspace("core/go-io/task-5", nil) + + core.AssertEqual(t, 0, subsystem.stateStoreCount(stateQueueGroup)) + core.AssertFalse(t, subsystem.Workspaces().Get("core/go-io/task-5").OK) +} + +func TestConcurrencySnapshot_PrepSubsystem_TrackWorkspace_Ugly(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{workspaces: core.NewRegistry[*WorkspaceStatus]()} + defer subsystem.closeStateStore() + subsystem.TrackWorkspace("core/go-io/task-5", &WorkspaceStatus{Status: "running", Agent: "codex:gpt-5.4", Repo: "go-io"}) + + value, ok := subsystem.stateStoreGet(stateConcurrencyGroup, "codex") + core.RequireTrue(t, ok) + core.AssertContains(t, value, "\"running\":1") +} + +func TestBaseOnly_PrepSubsystem_RegisterTools_Bad(t *testing.T) { + t.Setenv("CORE_MCP_FULL", "") + svc, err := coremcp.New(coremcp.Options{Unrestricted: true}) + core.RequireNoError(t, err) + + subsystem := &PrepSubsystem{} + subsystem.RegisterTools(svc) + toolNames := registerToolNames(t, svc) + + core.AssertContains(t, toolNames, "agentic_prep_workspace") + core.AssertNotContains(t, toolNames, "agentic_session_start") +} + +func TestRepeatedCall_PrepSubsystem_RegisterTools_Ugly(t *testing.T) { + t.Setenv("CORE_MCP_FULL", "1") + svc, err := coremcp.New(coremcp.Options{Unrestricted: true}) + core.RequireNoError(t, err) + + subsystem := &PrepSubsystem{} + subsystem.RegisterTools(svc) + subsystem.RegisterTools(svc) + toolNames := registerToolNames(t, svc) + + core.AssertContains(t, toolNames, "agentic_prep_workspace") + core.AssertContains(t, toolNames, "agentic_session_start") +} + +func TestUnknownMessage_PrepSubsystem_HandleIPCEvents_Bad(t *testing.T) { + c, subsystem := newCoreForHandlerTests(t) + result := subsystem.HandleIPCEvents(c, messages.PokeQueue{}) + + core.AssertTrue(t, result.OK) + core.AssertNil(t, result.Value) +} + +func TestMissingWorkspace_PrepSubsystem_HandleIPCEvents_Ugly(t *testing.T) { + c, subsystem := newCoreForHandlerTests(t) + result := subsystem.HandleIPCEvents(c, messages.SpawnQueued{Workspace: "missing", Agent: "claude", Task: "Write docs"}) + + core.AssertTrue(t, result.OK) + core.AssertNil(t, result.Value) +} + +func TestMissingToken_PrepSubsystem_Connect_Bad(t *testing.T) { + resetFleetRuntimeState() + t.Cleanup(resetFleetRuntimeState) + + subsystem := testPrepWithPlatformServer(t, nil, "") + result := subsystem.Connect(context.Background(), core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) + + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) +} + +func TestCancelled_PrepSubsystem_Connect_Ugly(t *testing.T) { + resetFleetRuntimeState() + originalHeartbeat := fleetHeartbeatInterval + t.Cleanup(func() { + fleetHeartbeatInterval = originalHeartbeat + resetFleetRuntimeState() + }) + + subsystem := testPrepWithPlatformServer(t, nil, "secret-token") + fleetHeartbeatInterval = 0 + ctx, cancel := context.WithCancel(context.Background()) + cancel() + result := subsystem.Connect(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) + + core.AssertTrue(t, result.OK) + core.AssertEqual(t, "offline", fleetRuntimeSnapshotValue().State) +} + +func TestMissingToken_PrepSubsystem_PollFallback_Bad(t *testing.T) { + resetFleetRuntimeState() + t.Cleanup(resetFleetRuntimeState) + + subsystem := testPrepWithPlatformServer(t, nil, "") + result := subsystem.PollFallback(context.Background(), core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) + + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) +} + +func TestCancelled_PrepSubsystem_PollFallback_Ugly(t *testing.T) { + resetFleetRuntimeState() + t.Cleanup(resetFleetRuntimeState) + + subsystem := testPrepWithPlatformServer(t, nil, "secret-token") + ctx, cancel := context.WithCancel(context.Background()) + cancel() + result := subsystem.PollFallback(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) + + core.AssertTrue(t, result.OK) + core.AssertNil(t, result.Value) +} + +func TestMissingToken_PrepSubsystem_Heartbeat_Bad(t *testing.T) { + resetFleetRuntimeState() + t.Cleanup(resetFleetRuntimeState) + + subsystem := testPrepWithPlatformServer(t, nil, "") + result := subsystem.Heartbeat(context.Background(), core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) + + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) +} + +func TestDisabledInterval_PrepSubsystem_Heartbeat_Ugly(t *testing.T) { + resetFleetRuntimeState() + originalHeartbeat := fleetHeartbeatInterval + t.Cleanup(func() { + fleetHeartbeatInterval = originalHeartbeat + resetFleetRuntimeState() + }) + + subsystem := testPrepWithPlatformServer(t, nil, "secret-token") + fleetHeartbeatInterval = 0 + result := subsystem.Heartbeat(context.Background(), core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) + + core.AssertTrue(t, result.OK) + core.AssertNil(t, result.Value) +} + +func TestPrepFailure_PrepSubsystem_DispatchSync_Bad(t *testing.T) { + subsystem := &PrepSubsystem{} + subsystem.dispatchSyncPrep = func(context.Context, *mcpsdk.CallToolRequest, PrepInput) (*mcpsdk.CallToolResult, PrepOutput, error) { + return nil, PrepOutput{}, core.E("prepWorkspace", "boom", nil) + } + + result := subsystem.DispatchSync(context.Background(), DispatchSyncInput{Repo: "go-io", Task: "Fix tests"}) + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Error) + core.AssertContains(t, result.Error.Error(), "prep workspace failed") +} + +func TestSpawnFailure_PrepSubsystem_DispatchSync_Ugly(t *testing.T) { + dir := t.TempDir() + setTestWorkspace(t, dir) + + workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-10") + subsystem := &PrepSubsystem{dispatchSyncTick: 10 * time.Millisecond} + subsystem.dispatchSyncPrep = func(context.Context, *mcpsdk.CallToolRequest, PrepInput) (*mcpsdk.CallToolResult, PrepOutput, error) { + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(&WorkspaceStatus{Status: "running"})).OK) + return nil, PrepOutput{Success: true, WorkspaceDir: workspaceDir, Prompt: "prompt"}, nil + } + subsystem.dispatchSyncSpawn = func(string, string, string) (int, string, string, error) { + return 0, "", "", core.E("spawn", "boom", nil) + } + + result := subsystem.DispatchSync(context.Background(), DispatchSyncInput{Repo: "go-io", Agent: "codex", Task: "Fix tests"}) + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Error) + core.AssertContains(t, result.Error.Error(), "spawn agent failed") +} + +func TestUnknownAgent_PrepSubsystem_SpawnFromQueue_Bad(t *testing.T) { + subsystem := newPrepWithProcess() + result := subsystem.SpawnFromQueue("robot-from-the-future", "Write docs", t.TempDir()) + + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "unknown agent") +} + +func TestMissingProcess_PrepSubsystem_SpawnFromQueue_Ugly(t *testing.T) { + writeFakeAgentBinary(t, "claude") + + c := core.New() + subsystem := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} + result := subsystem.SpawnFromQueue("claude", "Write docs", t.TempDir()) + + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "process service not registered") +} + +func TestNilArgs_RegisterHandlers_Bad(t *testing.T) { + core.AssertNotPanics(t, func() { + RegisterHandlers(nil, nil) + }) +} + +func TestRepeated_RegisterHandlers_Ugly(t *testing.T) { + c := core.New() + c.Action("runner.poke", func(context.Context, core.Options) core.Result { return core.Result{OK: true} }) + + RegisterHandlers(c, &PrepSubsystem{}) + RegisterHandlers(c, &PrepSubsystem{}) + core.AssertNotPanics(t, func() { + c.ACTION(messages.AgentCompleted{}) + }) +} + +func TestMissingIssue_ForgeMetaReader_GetIssueState_Bad(t *testing.T) { + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": newPipelineTestRepo()}) + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + _, err := reader.GetIssueState(context.Background(), "go-io", 77) + + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "issue") +} + +func TestDefaultState_ForgeMetaReader_GetIssueState_Ugly(t *testing.T) { + repo := newPipelineTestRepo() + repo.Issues[8] = &pipelineTestIssue{Number: 8, Title: "Untitled", State: ""} + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": repo}) + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + state, err := reader.GetIssueState(context.Background(), "go-io", 8) + + core.RequireNoError(t, err) + core.AssertEqual(t, "open", state.State) + core.AssertEqual(t, "Untitled", state.Title) +} + +func TestMissingPR_ForgeMetaReader_GetPRMeta_Bad(t *testing.T) { + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": newPipelineTestRepo()}) + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + _, err := reader.GetPRMeta(context.Background(), "go-io", 44) + + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "failed to read PR") +} + +func TestInvalidStatusPayload_ForgeMetaReader_GetPRMeta_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/repos/core/go-io/pulls/12": + _, _ = w.Write([]byte(`{"number":12,"state":"open","head":{"ref":"agent/fix","sha":"sha-12","repo":{"updated_at":"2026-04-25T12:00:00Z","pushed_at":"2026-04-25T12:00:00Z"}},"base":{"ref":"dev"},"reactions":{"eyes":0}}`)) + case "/api/v1/repos/core/go-io/commits/sha-12/status": + _, _ = w.Write([]byte(`not-json`)) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + t.Cleanup(srv.Close) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + meta, err := reader.GetPRMeta(context.Background(), "go-io", 12) + + core.RequireNoError(t, err) + core.AssertEqual(t, 12, meta.Number) + core.AssertLen(t, meta.Checks, 0) +} + +func TestMissingEpic_ForgeMetaReader_GetEpicMeta_Bad(t *testing.T) { + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": newPipelineTestRepo()}) + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + _, err := reader.GetEpicMeta(context.Background(), "go-io", 1) + + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "issue") +} + +func TestNoChildren_ForgeMetaReader_GetEpicMeta_Ugly(t *testing.T) { + repo := newPipelineTestRepo() + repo.Issues[1] = &pipelineTestIssue{Number: 1, Title: "Epic", State: "open", Body: "plain body"} + srv := newPipelineTestServer(t, map[string]*pipelineTestRepo{"go-io": repo}) + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + meta, err := reader.GetEpicMeta(context.Background(), "go-io", 1) + + core.RequireNoError(t, err) + core.AssertEmpty(t, meta.Branch) + core.AssertLen(t, meta.Children, 0) +} + +func TestMissingComment_ForgeMetaReader_GetCommentReactions_Bad(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + t.Cleanup(srv.Close) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + _, err := reader.GetCommentReactions(context.Background(), "go-io", 55) + + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "failed to read comment reactions") +} + +func TestInvalidPayload_ForgeMetaReader_GetCommentReactions_Ugly(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`not-json`)) + })) + t.Cleanup(srv.Close) + + subsystem, _ := testPrepWithCore(t, srv) + reader := &pipelineForgeMetaReader{subsystem: subsystem, org: "core"} + _, err := reader.GetCommentReactions(context.Background(), "go-io", 55) + + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "failed to decode reactions") +} diff --git a/pkg/agentic/ax7_paths_test.go b/pkg/agentic/ax7_paths_test.go new file mode 100644 index 00000000..c36a2228 --- /dev/null +++ b/pkg/agentic/ax7_paths_test.go @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "testing" + + core "dappco.re/go" +) + +func ax7WorkspaceDir(t *testing.T) string { + t.Helper() + + root := t.TempDir() + setTestWorkspace(t, root) + wsDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-5") + core.RequireTrue(t, fs.EnsureDir(WorkspaceRepoDir(wsDir)).OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceMetaDir(wsDir)).OK) + return wsDir +} + +func ax7WriteStatus(t *testing.T, workspaceDir string) { + t.Helper() + + core.RequireTrue(t, fs.Write(WorkspaceStatusPath(workspaceDir), core.JSONMarshalString(&WorkspaceStatus{ + Status: "running", + Agent: "codex", + Repo: "go-io", + Task: "AX7", + })).OK) +} + +func TestCoreHome_HomeDir_Good(t *testing.T) { + t.Setenv("CORE_HOME", "/tmp/core-home") + t.Setenv("HOME", "/tmp/home") + t.Setenv("DIR_HOME", "/tmp/dir-home") + + got := HomeDir() + core.AssertEqual(t, "/tmp/core-home", got) + core.AssertContains(t, got, "core-home") +} + +func TestHomeFallback_HomeDir_Bad(t *testing.T) { + t.Setenv("CORE_HOME", "") + t.Setenv("HOME", "/tmp/home") + t.Setenv("DIR_HOME", "/tmp/dir-home") + + got := HomeDir() + core.AssertEqual(t, "/tmp/home", got) + core.AssertContains(t, got, "home") +} + +func TestDirHomeFallback_HomeDir_Ugly(t *testing.T) { + t.Setenv("CORE_HOME", "") + t.Setenv("HOME", "") + t.Setenv("DIR_HOME", "/tmp/dir-home") + + got := HomeDir() + core.AssertEqual(t, "/tmp/dir-home", got) + core.AssertContains(t, got, "dir-home") +} + +func TestWorkspaceEnv_CoreRoot_Good(t *testing.T) { + setTestWorkspace(t, "/tmp/test-core") + got := CoreRoot() + core.AssertEqual(t, "/tmp/test-core", got) + core.AssertContains(t, got, "test-core") +} + +func TestWhitespaceWorkspace_CoreRoot_Bad(t *testing.T) { + setTestWorkspace(t, " ") + got := CoreRoot() + core.AssertEqual(t, " ", got) + core.AssertNotEmpty(t, got) +} + +func TestUnicodeWorkspace_CoreRoot_Ugly(t *testing.T) { + setTestWorkspace(t, "/tmp/\u00e9\u00e0\u00fc") + got := CoreRoot() + core.AssertEqual(t, "/tmp/\u00e9\u00e0\u00fc", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestWorkspaceEnv_WorkspaceRoot_Good(t *testing.T) { + setTestWorkspace(t, "/tmp/test-core") + got := WorkspaceRoot() + core.AssertEqual(t, "/tmp/test-core/workspace", got) + core.AssertContains(t, got, "workspace") +} + +func TestFallbackRoot_WorkspaceRoot_Bad(t *testing.T) { + setTestWorkspace(t, "") + got := WorkspaceRoot() + core.AssertContains(t, got, "/Code/.core/workspace") + core.AssertContains(t, got, "workspace") +} + +func TestTrailingSlash_WorkspaceRoot_Ugly(t *testing.T) { + setTestWorkspace(t, "/tmp/test-core/") + got := WorkspaceRoot() + core.AssertNotEmpty(t, got) + core.AssertContains(t, got, "workspace") +} + +func TestWorkspaceEnv_PlansRoot_Good(t *testing.T) { + setTestWorkspace(t, "/tmp/test-core") + got := PlansRoot() + core.AssertEqual(t, "/tmp/test-core/plans", got) + core.AssertContains(t, got, "plans") +} + +func TestFallbackRoot_PlansRoot_Bad(t *testing.T) { + setTestWorkspace(t, "") + got := PlansRoot() + core.AssertContains(t, got, "/Code/.core/plans") + core.AssertContains(t, got, "plans") +} + +func TestNestedRoot_PlansRoot_Ugly(t *testing.T) { + setTestWorkspace(t, "/a/b/c/d/e/f") + got := PlansRoot() + core.AssertEqual(t, "/a/b/c/d/e/f/plans", got) + core.AssertContains(t, got, "/plans") +} + +func TestExplicitAgent_AgentName_Good(t *testing.T) { + t.Setenv("AGENT_NAME", "clotho") + got := AgentName() + core.AssertEqual(t, "clotho", got) + core.AssertNotEmpty(t, got) +} + +func TestWhitespaceAgent_AgentName_Bad(t *testing.T) { + t.Setenv("AGENT_NAME", " ") + got := AgentName() + core.AssertEqual(t, " ", got) + core.AssertNotEmpty(t, got) +} + +func TestUnicodeAgent_AgentName_Ugly(t *testing.T) { + t.Setenv("AGENT_NAME", "\u00e9nchantr\u00efx") + got := AgentName() + core.AssertEqual(t, "\u00e9nchantr\u00efx", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestExplicitOrg_GitHubOrg_Good(t *testing.T) { + t.Setenv("GITHUB_ORG", "myorg") + got := GitHubOrg() + core.AssertEqual(t, "myorg", got) + core.AssertNotEmpty(t, got) +} + +func TestWhitespaceOrg_GitHubOrg_Bad(t *testing.T) { + t.Setenv("GITHUB_ORG", " ") + got := GitHubOrg() + core.AssertEqual(t, " ", got) + core.AssertNotEmpty(t, got) +} + +func TestSpecialCharsOrg_GitHubOrg_Ugly(t *testing.T) { + t.Setenv("GITHUB_ORG", "org/with/slashes") + got := GitHubOrg() + core.AssertEqual(t, "org/with/slashes", got) + core.AssertContains(t, got, "/") +} + +func TestUnrestrictedFs_LocalFs_Good(t *testing.T) { + got := LocalFs() + core.AssertNotNil(t, got) + assertIsType(t, &core.Fs{}, got) +} + +func TestMissingFile_LocalFs_Bad(t *testing.T) { + result := LocalFs().Read("/tmp/nonexistent-path-agentic-ax7/file.txt") + core.AssertFalse(t, result.OK) + core.AssertNotNil(t, result.Value) +} + +func TestEmptyPath_LocalFs_Ugly(t *testing.T) { + core.AssertNotPanics(t, func() { + LocalFs().Read("") + }) + core.AssertNotNil(t, LocalFs()) +} + +func TestWorkspaceStatusFile_WorkspaceStatusPath_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceStatusPath(wsDir) + core.AssertEqual(t, core.JoinPath(wsDir, "status.json"), got) + core.AssertContains(t, got, "status.json") +} + +func TestEmptyDir_WorkspaceStatusPath_Bad(t *testing.T) { + got := WorkspaceStatusPath("") + core.AssertEqual(t, "/status.json", got) + core.AssertContains(t, got, "status.json") +} + +func TestUnicodeDir_WorkspaceStatusPath_Ugly(t *testing.T) { + got := WorkspaceStatusPath("/tmp/\u00e9") + core.AssertEqual(t, "/tmp/\u00e9/status.json", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestStandardName_WorkspaceName_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceName(wsDir) + core.AssertEqual(t, "core/go-io/task-5", got) + core.AssertContains(t, got, "go-io") +} + +func TestRootFallback_WorkspaceName_Bad(t *testing.T) { + root := t.TempDir() + setTestWorkspace(t, root) + got := WorkspaceName(WorkspaceRoot()) + core.AssertEqual(t, "workspace", got) + core.AssertContains(t, got, "workspace") +} + +func TestSlashName_WorkspaceName_Ugly(t *testing.T) { + root := t.TempDir() + setTestWorkspace(t, root) + wsDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "feature", "new-ui") + got := WorkspaceName(wsDir) + core.AssertEqual(t, "core/go-io/feature/new-ui", got) + core.AssertContains(t, got, "feature") +} + +func TestRepoPath_WorkspaceRepoDir_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceRepoDir(wsDir) + core.AssertEqual(t, core.JoinPath(wsDir, "repo"), got) + core.AssertContains(t, got, "/repo") +} + +func TestEmptyDir_WorkspaceRepoDir_Bad(t *testing.T) { + got := WorkspaceRepoDir("") + core.AssertEqual(t, "/repo", got) + core.AssertContains(t, got, "repo") +} + +func TestUnicodeDir_WorkspaceRepoDir_Ugly(t *testing.T) { + got := WorkspaceRepoDir("/tmp/\u00e9") + core.AssertEqual(t, "/tmp/\u00e9/repo", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestMetaPath_WorkspaceMetaDir_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceMetaDir(wsDir) + core.AssertEqual(t, core.JoinPath(wsDir, ".meta"), got) + core.AssertContains(t, got, ".meta") +} + +func TestEmptyDir_WorkspaceMetaDir_Bad(t *testing.T) { + got := WorkspaceMetaDir("") + core.AssertEqual(t, "/.meta", got) + core.AssertContains(t, got, ".meta") +} + +func TestUnicodeDir_WorkspaceMetaDir_Ugly(t *testing.T) { + got := WorkspaceMetaDir("/tmp/\u00e9") + core.AssertEqual(t, "/tmp/\u00e9/.meta", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestBlockedPath_WorkspaceBlockedPath_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceBlockedPath(wsDir) + core.AssertEqual(t, core.JoinPath(wsDir, "repo", "BLOCKED.md"), got) + core.AssertContains(t, got, "BLOCKED.md") +} + +func TestEmptyDir_WorkspaceBlockedPath_Bad(t *testing.T) { + got := WorkspaceBlockedPath("") + core.AssertEqual(t, "/repo/BLOCKED.md", got) + core.AssertContains(t, got, "BLOCKED.md") +} + +func TestUnicodeDir_WorkspaceBlockedPath_Ugly(t *testing.T) { + got := WorkspaceBlockedPath("/tmp/\u00e9") + core.AssertEqual(t, "/tmp/\u00e9/repo/BLOCKED.md", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestAnswerPath_WorkspaceAnswerPath_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceAnswerPath(wsDir) + core.AssertEqual(t, core.JoinPath(wsDir, "repo", "ANSWER.md"), got) + core.AssertContains(t, got, "ANSWER.md") +} + +func TestEmptyDir_WorkspaceAnswerPath_Bad(t *testing.T) { + got := WorkspaceAnswerPath("") + core.AssertEqual(t, "/repo/ANSWER.md", got) + core.AssertContains(t, got, "ANSWER.md") +} + +func TestUnicodeDir_WorkspaceAnswerPath_Ugly(t *testing.T) { + got := WorkspaceAnswerPath("/tmp/\u00e9") + core.AssertEqual(t, "/tmp/\u00e9/repo/ANSWER.md", got) + core.AssertContains(t, got, "\u00e9") +} + +func TestExistingLogs_WorkspaceLogFiles_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + logPath := core.JoinPath(WorkspaceMetaDir(wsDir), "agent-codex.log") + core.RequireTrue(t, fs.Write(logPath, "done").OK) + + got := WorkspaceLogFiles(wsDir) + core.AssertContains(t, got, logPath) + core.AssertLen(t, got, 1) +} + +func TestMissingLogs_WorkspaceLogFiles_Bad(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + got := WorkspaceLogFiles(wsDir) + core.AssertEmpty(t, got) + core.AssertLen(t, got, 0) +} + +func TestMultipleLogs_WorkspaceLogFiles_Ugly(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + first := core.JoinPath(WorkspaceMetaDir(wsDir), "agent-codex.log") + second := core.JoinPath(WorkspaceMetaDir(wsDir), "agent-claude.log") + core.RequireTrue(t, fs.Write(first, "done").OK) + core.RequireTrue(t, fs.Write(second, "done").OK) + + got := WorkspaceLogFiles(wsDir) + core.AssertContains(t, got, first) + core.AssertContains(t, got, second) +} + +func TestWorkspaceTree_WorkspaceStatusPaths_Good(t *testing.T) { + root := t.TempDir() + setTestWorkspace(t, root) + wsDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-5") + core.RequireTrue(t, fs.EnsureDir(WorkspaceRepoDir(wsDir)).OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceMetaDir(wsDir)).OK) + ax7WriteStatus(t, wsDir) + + got := WorkspaceStatusPaths() + core.AssertContains(t, got, WorkspaceStatusPath(wsDir)) + core.AssertLen(t, got, 1) +} + +func TestEmptyTree_WorkspaceStatusPaths_Bad(t *testing.T) { + root := t.TempDir() + setTestWorkspace(t, root) + got := WorkspaceStatusPaths() + core.AssertEmpty(t, got) + core.AssertLen(t, got, 0) +} + +func TestDeepTree_WorkspaceStatusPaths_Ugly(t *testing.T) { + root := t.TempDir() + setTestWorkspace(t, root) + shallow := core.JoinPath(WorkspaceRoot(), "ws-flat") + deep := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-12") + core.RequireTrue(t, fs.EnsureDir(shallow).OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceRepoDir(deep)).OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceMetaDir(deep)).OK) + core.RequireTrue(t, fs.Write(WorkspaceStatusPath(shallow), "{}").OK) + ax7WriteStatus(t, deep) + + got := WorkspaceStatusPaths() + core.AssertContains(t, got, WorkspaceStatusPath(shallow)) + core.AssertContains(t, got, WorkspaceStatusPath(deep)) +} + +func TestStatusFile_ReadStatusResult_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + ax7WriteStatus(t, wsDir) + + result := ReadStatusResult(wsDir) + core.RequireTrue(t, result.OK) + status, ok := result.Value.(*WorkspaceStatus) + core.RequireTrue(t, ok) + core.AssertEqual(t, "running", status.Status) +} + +func TestMissingStatus_ReadStatusResult_Bad(t *testing.T) { + result := ReadStatusResult(t.TempDir()) + core.AssertFalse(t, result.OK) + core.AssertNotNil(t, result.Value) +} + +func TestInvalidJSON_ReadStatusResult_Ugly(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + core.RequireTrue(t, fs.Write(WorkspaceStatusPath(wsDir), "{not-json").OK) + + result := ReadStatusResult(wsDir) + core.AssertFalse(t, result.OK) + core.AssertNotNil(t, result.Value) +} + +func TestStatusFile_ReadStatus_Good(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + ax7WriteStatus(t, wsDir) + + status, err := ReadStatus(wsDir) + core.RequireNoError(t, err) + core.AssertEqual(t, "running", status.Status) +} + +func TestMissingStatus_ReadStatus_Bad(t *testing.T) { + status, err := ReadStatus(t.TempDir()) + core.AssertNil(t, status) + core.AssertError(t, err) +} + +func TestInvalidJSON_ReadStatus_Ugly(t *testing.T) { + wsDir := ax7WorkspaceDir(t) + core.RequireTrue(t, fs.Write(WorkspaceStatusPath(wsDir), "{not-json").OK) + + status, err := ReadStatus(wsDir) + core.AssertNil(t, status) + core.AssertError(t, err) +} diff --git a/pkg/agentic/ax7_provider_manager_test.go b/pkg/agentic/ax7_provider_manager_test.go new file mode 100644 index 00000000..80b2613a --- /dev/null +++ b/pkg/agentic/ax7_provider_manager_test.go @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "context" + "testing" + + core "dappco.re/go" +) + +func TestBuiltIns_NewProviderManager_Good(t *testing.T) { + manager := NewProviderManager(func(context.Context, string, map[string]any) (string, error) { + return "Draft ready", nil + }) + core.AssertNotNil(t, manager) + core.AssertContains(t, manager.Names(), "claude") +} + +func TestNilGenerator_NewProviderManager_Bad(t *testing.T) { + manager := NewProviderManager(nil) + core.AssertNotNil(t, manager) + core.AssertContains(t, manager.Names(), "openai") +} + +func TestFreshManager_NewProviderManager_Ugly(t *testing.T) { + first := NewProviderManager(nil) + second := NewProviderManager(nil) + core.AssertTrue(t, first != second) + core.AssertEqual(t, first.Names(), second.Names()) +} + +func TestSortedNames_ProviderManager_Names_Good(t *testing.T) { + manager := NewProviderManager(nil) + got := manager.Names() + core.AssertEqual(t, []string{"claude", "gemini", "openai"}, got) + core.AssertLen(t, got, 3) +} + +func TestEmptyNames_ProviderManager_Names_Bad(t *testing.T) { + manager := &ProviderManager{} + got := manager.Names() + core.AssertNil(t, got) + core.AssertLen(t, got, 0) +} + +func TestNilManager_ProviderManager_Names_Ugly(t *testing.T) { + var manager *ProviderManager + got := manager.Names() + core.AssertNil(t, got) + core.AssertLen(t, got, 0) +} + +func TestCaseInsensitive_ProviderManager_Provider_Good(t *testing.T) { + manager := NewProviderManager(nil) + provider, ok := manager.Provider("CLAUDE") + core.AssertTrue(t, ok) + core.AssertEqual(t, "claude", provider.Name()) +} + +func TestUnknown_ProviderManager_Provider_Bad(t *testing.T) { + manager := NewProviderManager(nil) + provider, ok := manager.Provider("unknown") + core.AssertFalse(t, ok) + core.AssertNil(t, provider) +} + +func TestNilManager_ProviderManager_Provider_Ugly(t *testing.T) { + var manager *ProviderManager + provider, ok := manager.Provider("claude") + core.AssertFalse(t, ok) + core.AssertNil(t, provider) +} + +func TestRegisterProvider_ProviderManager_Register_Good(t *testing.T) { + manager := &ProviderManager{} + provider := newContentProvider("custom", "gpt-5.4", true, nil) + manager.Register(provider) + core.AssertContains(t, manager.Names(), "custom") + core.AssertEqual(t, provider, manager.DefaultProvider()) +} + +func TestNilProvider_ProviderManager_Register_Bad(t *testing.T) { + manager := &ProviderManager{} + manager.Register(nil) + core.AssertNil(t, manager.Names()) + core.AssertNil(t, manager.DefaultProvider()) +} + +func TestReplaceProvider_ProviderManager_Register_Ugly(t *testing.T) { + manager := &ProviderManager{} + first := newContentProvider("custom", "v1", true, nil) + second := newContentProvider("custom", "v2", true, nil) + manager.Register(first) + manager.Register(second) + provider, ok := manager.Provider("custom") + core.AssertTrue(t, ok) + core.AssertEqual(t, "v2", provider.DefaultModel()) +} + +func TestAvailableProvider_ProviderManager_DefaultProvider_Good(t *testing.T) { + manager := &ProviderManager{} + manager.Register(newContentProvider("custom", "gpt-5.4", true, nil)) + provider := manager.DefaultProvider() + core.AssertNotNil(t, provider) + core.AssertEqual(t, "custom", provider.Name()) +} + +func TestNoAvailableProvider_ProviderManager_DefaultProvider_Bad(t *testing.T) { + manager := &ProviderManager{} + manager.Register(newContentProvider("custom", "gpt-5.4", false, nil)) + provider := manager.DefaultProvider() + core.AssertNil(t, provider) + core.AssertFalse(t, provider != nil) +} + +func TestNilManager_ProviderManager_DefaultProvider_Ugly(t *testing.T) { + var manager *ProviderManager + provider := manager.DefaultProvider() + core.AssertNil(t, provider) + core.AssertFalse(t, provider != nil) +} + +func TestNamedProvider_Provider_Name_Good(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, nil) + got := provider.Name() + core.AssertEqual(t, "claude", got) + core.AssertNotEmpty(t, got) +} + +func TestEmptyName_Provider_Name_Bad(t *testing.T) { + provider := newContentProvider("", "claude-3.7-sonnet", true, nil) + got := provider.Name() + core.AssertEqual(t, "", got) + core.AssertEmpty(t, got) +} + +func TestUnicodeName_Provider_Name_Ugly(t *testing.T) { + provider := newContentProvider("cl\u00e1ude", "claude-3.7-sonnet", true, nil) + got := provider.Name() + core.AssertEqual(t, "cl\u00e1ude", got) + core.AssertContains(t, got, "\u00e1") +} + +func TestNamedModel_Provider_DefaultModel_Good(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, nil) + got := provider.DefaultModel() + core.AssertEqual(t, "claude-3.7-sonnet", got) + core.AssertContains(t, got, "sonnet") +} + +func TestEmptyModel_Provider_DefaultModel_Bad(t *testing.T) { + provider := newContentProvider("claude", "", true, nil) + got := provider.DefaultModel() + core.AssertEqual(t, "", got) + core.AssertEmpty(t, got) +} + +func TestUnicodeModel_Provider_DefaultModel_Ugly(t *testing.T) { + provider := newContentProvider("claude", "m\u00f6del", true, nil) + got := provider.DefaultModel() + core.AssertEqual(t, "m\u00f6del", got) + core.AssertContains(t, got, "\u00f6") +} + +func TestAvailableProvider_Provider_IsAvailable_Good(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, nil) + core.AssertTrue(t, provider.IsAvailable()) + core.AssertEqual(t, true, provider.IsAvailable()) +} + +func TestUnavailableProvider_Provider_IsAvailable_Bad(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", false, nil) + core.AssertFalse(t, provider.IsAvailable()) + core.AssertEqual(t, false, provider.IsAvailable()) +} + +func TestToggleAvailability_Provider_IsAvailable_Ugly(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, nil) + provider.available = false + core.AssertFalse(t, provider.IsAvailable()) + core.AssertEqual(t, false, provider.IsAvailable()) +} + +func TestGenerateContent_Provider_Generate_Good(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, func(_ context.Context, _ string, options map[string]any) (string, error) { + core.AssertEqual(t, "claude", options["provider"]) + return "Draft ready", nil + }) + text, err := provider.Generate(context.Background(), "Write a release note", nil) + core.RequireNoError(t, err) + core.AssertEqual(t, "Draft ready", text) +} + +func TestMissingGenerator_Provider_Generate_Bad(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, nil) + text, err := provider.Generate(context.Background(), "Write a release note", nil) + core.AssertEqual(t, "", text) + core.AssertError(t, err) +} + +func TestRetryGenerate_Provider_Generate_Ugly(t *testing.T) { + attempts := 0 + provider := newContentProvider("claude", "claude-3.7-sonnet", true, func(_ context.Context, _ string, _ map[string]any) (string, error) { + attempts++ + if attempts == 1 { + return "", core.E("test.generate", "transient failure", nil) + } + return "Draft ready", nil + }) + text, err := provider.Generate(context.Background(), "Write a release note", nil) + core.RequireNoError(t, err) + core.AssertEqual(t, "Draft ready", text) +} + +func TestStreamContent_Provider_Stream_Good(t *testing.T) { + var tokens []string + provider := newContentProvider("claude", "claude-3.7-sonnet", true, func(_ context.Context, _ string, _ map[string]any) (string, error) { + return "Draft ready", nil + }) + err := provider.Stream(context.Background(), "Write a release note", nil, func(token string) { + tokens = append(tokens, token) + }) + core.RequireNoError(t, err) + core.AssertEqual(t, []string{"Draft ready"}, tokens) +} + +func TestMissingStream_Provider_Stream_Bad(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, nil) + provider.stream = nil + err := provider.Stream(context.Background(), "Write a release note", nil, nil) + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "provider not configured") +} + +func TestNilCallback_Provider_Stream_Ugly(t *testing.T) { + provider := newContentProvider("claude", "claude-3.7-sonnet", true, func(_ context.Context, _ string, _ map[string]any) (string, error) { + return "Draft ready", nil + }) + err := provider.Stream(context.Background(), "Write a release note", nil, nil) + core.RequireNoError(t, err) + core.AssertTrue(t, true) +} diff --git a/pkg/agentic/brain_client.go b/pkg/agentic/brain_client.go index 46506b38..b1d7909d 100644 --- a/pkg/agentic/brain_client.go +++ b/pkg/agentic/brain_client.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" brainclient "dappco.re/go/mcp/pkg/mcp/brain/client" ) diff --git a/pkg/agentic/brain_seed_memory.go b/pkg/agentic/brain_seed_memory.go index 509b952d..14fd9cd6 100644 --- a/pkg/agentic/brain_seed_memory.go +++ b/pkg/agentic/brain_seed_memory.go @@ -6,7 +6,7 @@ import ( "context" "slices" - core "dappco.re/go/core" + core "dappco.re/go" ) const brainSeedMemoryDefaultAgent = "virgil" diff --git a/pkg/agentic/brain_seed_memory_test.go b/pkg/agentic/brain_seed_memory_test.go index dd053266..563b4485 100644 --- a/pkg/agentic/brain_seed_memory_test.go +++ b/pkg/agentic/brain_seed_memory_test.go @@ -8,9 +8,7 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestBrainSeedMemory_CmdBrainSeedMemory_Good(t *testing.T) { @@ -19,17 +17,17 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Good(t *testing.T) { projectsDir := core.JoinPath(home, ".claude", "projects") memoryDir := core.JoinPath(home, ".claude", "projects", "-Users-snider-Code-eaas", "memory") - require.True(t, fs.EnsureDir(memoryDir).OK) - require.True(t, fs.Write(core.JoinPath(memoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().\n\n## Decision\nPrefer named actions.").OK) - require.True(t, fs.Write(core.JoinPath(memoryDir, "notes.md"), "## Convention\nUse UK English.\n").OK) + core.RequireTrue(t, fs.EnsureDir(memoryDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(memoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().\n\n## Decision\nPrefer named actions.").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(memoryDir, "notes.md"), "## Convention\nUse UK English.\n").OK) var bodies []map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/brain/remember", r.URL.Path) + core.AssertEqual(t, "/v1/brain/remember", r.URL.Path) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) bodies = append(bodies, payload) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"memory":{"id":"mem-1"}}`)) @@ -47,25 +45,28 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Good(t *testing.T) { core.Option{Key: "agent", Value: "virgil"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(BrainSeedMemoryOutput) - require.True(t, ok) - assert.Equal(t, 1, output.Files) - assert.Equal(t, 2, output.Imported) - assert.Equal(t, 0, output.Skipped) - assert.Equal(t, false, output.DryRun) - assert.Equal(t, projectsDir, output.Path) - require.Len(t, bodies, 2) - - assert.Equal(t, float64(42), bodies[0]["workspace_id"]) - assert.Equal(t, "virgil", bodies[0]["agent_id"]) - assert.Equal(t, "architecture", bodies[0]["type"]) - assert.Equal(t, "eaas", bodies[0]["project"]) - assert.Contains(t, bodies[0]["content"].(string), "Architecture") - assert.Equal(t, []any{"memory-import"}, bodies[0]["tags"]) - - assert.Equal(t, "decision", bodies[1]["type"]) - assert.Equal(t, []any{"memory-import"}, bodies[1]["tags"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, output.Files) + core.AssertEqual(t, 2, output.Imported) + core.AssertEqual(t, 0, output.Skipped) + core.AssertEqual(t, false, output.DryRun) + core.AssertEqual(t, projectsDir, output.Path) + core.AssertLen(t, bodies, 2) + if len(bodies) != 2 { + return + } + + core.AssertEqual(t, float64(42), bodies[0]["workspace_id"]) + core.AssertEqual(t, "virgil", bodies[0]["agent_id"]) + core.AssertEqual(t, "architecture", bodies[0]["type"]) + core.AssertEqual(t, "eaas", bodies[0]["project"]) + core.AssertContains(t, bodies[0]["content"].(string), "Architecture") + core.AssertEqual(t, []any{"memory-import"}, bodies[0]["tags"]) + + core.AssertEqual(t, "decision", bodies[1]["type"]) + core.AssertEqual(t, []any{"memory-import"}, bodies[1]["tags"]) } func TestBrainSeedMemory_CmdBrainSeedMemory_Good_GlobPath(t *testing.T) { @@ -75,18 +76,18 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Good_GlobPath(t *testing.T) { projectsDir := core.JoinPath(home, ".claude", "projects") firstMemoryDir := core.JoinPath(projectsDir, "-Users-snider-Code-eaas", "memory") secondMemoryDir := core.JoinPath(projectsDir, "-Users-snider-Code-agent", "memory") - require.True(t, fs.EnsureDir(firstMemoryDir).OK) - require.True(t, fs.EnsureDir(secondMemoryDir).OK) - require.True(t, fs.Write(core.JoinPath(firstMemoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().").OK) - require.True(t, fs.Write(core.JoinPath(secondMemoryDir, "MEMORY.md"), "# Memory\n\n## Decision\nPrefer named actions.").OK) + core.RequireTrue(t, fs.EnsureDir(firstMemoryDir).OK) + core.RequireTrue(t, fs.EnsureDir(secondMemoryDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(firstMemoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(secondMemoryDir, "MEMORY.md"), "# Memory\n\n## Decision\nPrefer named actions.").OK) var bodies []map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/brain/remember", r.URL.Path) + core.AssertEqual(t, "/v1/brain/remember", r.URL.Path) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) bodies = append(bodies, payload) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"memory":{"id":"mem-1"}}`)) @@ -103,15 +104,18 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Good_GlobPath(t *testing.T) { core.Option{Key: "path", Value: core.JoinPath(projectsDir, "*", "memory")}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(BrainSeedMemoryOutput) - require.True(t, ok) - assert.Equal(t, 2, output.Files) - assert.Equal(t, 2, output.Imported) - assert.Equal(t, 0, output.Skipped) - assert.Equal(t, core.JoinPath(projectsDir, "*", "memory"), output.Path) - require.Len(t, bodies, 2) - assert.ElementsMatch(t, []any{"architecture", "decision"}, []any{bodies[0]["type"], bodies[1]["type"]}) + core.RequireTrue(t, ok) + core.AssertEqual(t, 2, output.Files) + core.AssertEqual(t, 2, output.Imported) + core.AssertEqual(t, 0, output.Skipped) + core.AssertEqual(t, core.JoinPath(projectsDir, "*", "memory"), output.Path) + core.AssertLen(t, bodies, 2) + if len(bodies) != 2 { + return + } + core.AssertElementsMatch(t, []any{"architecture", "decision"}, []any{bodies[0]["type"], bodies[1]["type"]}) } func TestBrainSeedMemory_CmdBrainIngest_Good(t *testing.T) { @@ -119,16 +123,16 @@ func TestBrainSeedMemory_CmdBrainIngest_Good(t *testing.T) { t.Setenv("CORE_HOME", home) memoryDir := core.JoinPath(home, ".claude", "projects", "-Users-snider-Code-eaas", "memory") - require.True(t, fs.EnsureDir(memoryDir).OK) - require.True(t, fs.Write(core.JoinPath(memoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().").OK) + core.RequireTrue(t, fs.EnsureDir(memoryDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(memoryDir, "MEMORY.md"), "# Memory\n\n## Architecture\nUse Core.Process().").OK) var bodies []map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/brain/remember", r.URL.Path) + core.AssertEqual(t, "/v1/brain/remember", r.URL.Path) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) bodies = append(bodies, payload) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"memory":{"id":"mem-1"}}`)) @@ -145,15 +149,18 @@ func TestBrainSeedMemory_CmdBrainIngest_Good(t *testing.T) { core.Option{Key: "path", Value: memoryDir}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(BrainSeedMemoryOutput) - require.True(t, ok) - assert.Equal(t, 1, output.Files) - assert.Equal(t, 1, output.Imported) - assert.Equal(t, 0, output.Skipped) - require.Len(t, bodies, 1) - assert.Equal(t, float64(42), bodies[0]["workspace_id"]) - assert.Equal(t, "architecture", bodies[0]["type"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, output.Files) + core.AssertEqual(t, 1, output.Imported) + core.AssertEqual(t, 0, output.Skipped) + core.AssertLen(t, bodies, 1) + if len(bodies) != 1 { + return + } + core.AssertEqual(t, float64(42), bodies[0]["workspace_id"]) + core.AssertEqual(t, "architecture", bodies[0]["type"]) } func TestBrainSeedMemory_CmdBrainIngest_Good_DirectMarkdownFile(t *testing.T) { @@ -161,15 +168,15 @@ func TestBrainSeedMemory_CmdBrainIngest_Good_DirectMarkdownFile(t *testing.T) { t.Setenv("CORE_HOME", home) memoryFile := core.JoinPath(home, "notes.md") - require.True(t, fs.Write(memoryFile, "# Memory\n\n## Convention\nUse named actions.\n").OK) + core.RequireTrue(t, fs.Write(memoryFile, "# Memory\n\n## Convention\nUse named actions.\n").OK) var bodies []map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/brain/remember", r.URL.Path) + core.AssertEqual(t, "/v1/brain/remember", r.URL.Path) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) bodies = append(bodies, payload) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"memory":{"id":"mem-1"}}`)) @@ -186,16 +193,19 @@ func TestBrainSeedMemory_CmdBrainIngest_Good_DirectMarkdownFile(t *testing.T) { core.Option{Key: "path", Value: memoryFile}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(BrainSeedMemoryOutput) - require.True(t, ok) - assert.Equal(t, 1, output.Files) - assert.Equal(t, 1, output.Imported) - assert.Equal(t, 0, output.Skipped) - assert.Equal(t, memoryFile, output.Path) - require.Len(t, bodies, 1) - assert.Equal(t, "convention", bodies[0]["type"]) - assert.Contains(t, bodies[0]["content"].(string), "Use named actions.") + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, output.Files) + core.AssertEqual(t, 1, output.Imported) + core.AssertEqual(t, 0, output.Skipped) + core.AssertEqual(t, memoryFile, output.Path) + core.AssertLen(t, bodies, 1) + if len(bodies) != 1 { + return + } + core.AssertEqual(t, "convention", bodies[0]["type"]) + core.AssertContains(t, bodies[0]["content"].(string), "Use named actions.") } func TestBrainSeedMemory_CmdBrainSeedMemory_Bad_MissingWorkspace(t *testing.T) { @@ -205,10 +215,10 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Bad_MissingWorkspace(t *testing.T) { core.Option{Key: "path", Value: "/tmp/memory"}, )) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "workspace is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "workspace is required") } func TestBrainSeedMemory_CmdBrainIngest_Bad_MissingWorkspace(t *testing.T) { @@ -218,10 +228,10 @@ func TestBrainSeedMemory_CmdBrainIngest_Bad_MissingWorkspace(t *testing.T) { core.Option{Key: "path", Value: "/tmp/memory"}, )) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "workspace is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "workspace is required") } func TestBrainSeedMemory_CmdBrainSeedMemory_Ugly_PartialImportFailure(t *testing.T) { @@ -229,16 +239,16 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Ugly_PartialImportFailure(t *testing t.Setenv("CORE_HOME", home) memoryDir := core.JoinPath(home, ".claude", "projects", "-Users-snider-Code-eaas", "memory") - require.True(t, fs.EnsureDir(memoryDir).OK) - require.True(t, fs.Write(core.JoinPath(memoryDir, "MEMORY.md"), "## Architecture\nUse Core.Process().\n\n## Decision\nPrefer named actions.").OK) + core.RequireTrue(t, fs.EnsureDir(memoryDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(memoryDir, "MEMORY.md"), "## Architecture\nUse Core.Process().\n\n## Decision\nPrefer named actions.").OK) var calls int srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) if calls == 1 { http.Error(w, "boom", http.StatusInternalServerError) return @@ -259,10 +269,12 @@ func TestBrainSeedMemory_CmdBrainSeedMemory_Ugly_PartialImportFailure(t *testing Path: memoryDir, }, true) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(BrainSeedMemoryOutput) - require.True(t, ok) - assert.Equal(t, 1, output.Imported) - assert.Equal(t, 1, output.Skipped) - assert.Equal(t, 2, calls) + core.RequireTrue(t, ok) + // The brain client retries transient 5xx failures, so the first section + // succeeds on retry and the second section imports normally. + core.AssertEqual(t, 2, output.Imported) + core.AssertEqual(t, 0, output.Skipped) + core.AssertEqual(t, 3, calls) } diff --git a/pkg/agentic/branch_cleanup.go b/pkg/agentic/branch_cleanup.go index 1d406237..0edd93aa 100644 --- a/pkg/agentic/branch_cleanup.go +++ b/pkg/agentic/branch_cleanup.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/forge" ) diff --git a/pkg/agentic/branch_cleanup_test.go b/pkg/agentic/branch_cleanup_test.go index 0a53050a..e95426e4 100644 --- a/pkg/agentic/branch_cleanup_test.go +++ b/pkg/agentic/branch_cleanup_test.go @@ -10,10 +10,8 @@ import ( "testing" "time" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/forge" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCleanupBranch_Good_DeletesAgentBranch(t *testing.T) { @@ -25,9 +23,9 @@ func TestCleanupBranch_Good_DeletesAgentBranch(t *testing.T) { s := newCleanupPrep(server.URL) result := s.cleanupBranch(context.Background(), "core/go-io", branch) - require.True(t, result.OK) - assert.Equal(t, 1, state.deleteCalls) - assert.False(t, cleanupRemoteBranchExists(remoteDir, branch)) + core.RequireTrue(t, result.OK) + core.AssertEqual(t, 1, state.deleteCalls) + core.AssertFalse(t, cleanupRemoteBranchExists(remoteDir, branch)) } func TestCleanupBranch_Bad_RefuseProtected(t *testing.T) { @@ -41,11 +39,11 @@ func TestCleanupBranch_Bad_RefuseProtected(t *testing.T) { s := newCleanupPrep(server.URL) result := s.cleanupBranch(context.Background(), "core/go-io", "main") - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, _ := result.Value.(error) - require.Error(t, err) - assert.Contains(t, err.Error(), "refusing to delete protected branch") - assert.False(t, called) + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "refusing to delete protected branch") + core.AssertFalse(t, called) } func TestCleanupBranch_Ugly_DeleteFailsForge(t *testing.T) { @@ -55,11 +53,11 @@ func TestCleanupBranch_Ugly_DeleteFailsForge(t *testing.T) { result := s.cleanupBranch(context.Background(), "core/go-io", branch) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, _ := result.Value.(error) - require.Error(t, err) - assert.Contains(t, err.Error(), "failed to delete branch") - assert.Equal(t, 1, state.deleteCalls) + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "failed to delete branch") + core.AssertEqual(t, 1, state.deleteCalls) } func TestCleanupBranch_Good_CreatePRSuccessPathDeletesBranch(t *testing.T) { @@ -77,7 +75,7 @@ func TestCleanupBranch_Good_CreatePRSuccessPathDeletesBranch(t *testing.T) { repoDir := WorkspaceRepoDir(workspaceDir) createCleanupWorkspaceRepo(t, repoDir, branch) - require.NoError(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "completed", Agent: "codex", Repo: "go-io", @@ -88,12 +86,12 @@ func TestCleanupBranch_Good_CreatePRSuccessPathDeletesBranch(t *testing.T) { })) _, output, err := s.createPR(context.Background(), nil, CreatePRInput{Workspace: "test-ws"}) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Equal(t, "https://forge.test/core/go-io/pulls/42", output.PRURL) - assert.Equal(t, 1, state.prCalls) - assert.Equal(t, 1, state.deleteCalls) - assert.False(t, cleanupRemoteBranchExists(remoteDir, branch)) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "https://forge.test/core/go-io/pulls/42", output.PRURL) + core.AssertEqual(t, 1, state.prCalls) + core.AssertEqual(t, 1, state.deleteCalls) + core.AssertFalse(t, cleanupRemoteBranchExists(remoteDir, branch)) } func TestCleanupBranch_Good_CmdCompleteSuccessPathDeletesBranch(t *testing.T) { @@ -126,8 +124,8 @@ func TestCleanupBranch_Good_CmdCompleteSuccessPathDeletesBranch(t *testing.T) { } workspaceDir := core.JoinPath(root, "workspace", "test-ws") - require.True(t, fs.EnsureDir(workspaceDir).OK) - require.NoError(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireNoError(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Org: "core", @@ -136,9 +134,9 @@ func TestCleanupBranch_Good_CmdCompleteSuccessPathDeletesBranch(t *testing.T) { })) result := s.cmdComplete(core.NewOptions(core.Option{Key: "workspace", Value: workspaceDir})) - require.True(t, result.OK) - assert.Equal(t, 1, state.deleteCalls) - assert.False(t, cleanupRemoteBranchExists(remoteDir, branch)) + core.RequireTrue(t, result.OK) + core.AssertEqual(t, 1, state.deleteCalls) + core.AssertFalse(t, cleanupRemoteBranchExists(remoteDir, branch)) } type cleanupForgeServerState struct { @@ -162,8 +160,8 @@ func newCleanupRemoteRepo(t *testing.T) (string, string) { remoteRoot := t.TempDir() remoteDir := core.JoinPath(remoteRoot, "core", "go-io.git") - require.True(t, fs.EnsureDir(core.PathDir(remoteDir)).OK) - require.True(t, testCore.Process().Run(context.Background(), "git", "init", "--bare", remoteDir).OK) + core.RequireTrue(t, fs.EnsureDir(core.PathDir(remoteDir)).OK) + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "init", "--bare", remoteDir).OK) return remoteRoot, remoteDir } @@ -173,26 +171,26 @@ func seedCleanupRemoteBranch(t *testing.T, remoteDir, branch string) { repoDir := core.JoinPath(t.TempDir(), "seed-repo") createCleanupWorkspaceRepo(t, repoDir, branch) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "remote", "add", "origin", remoteDir).OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "push", "origin", "dev").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "push", "origin", branch).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "remote", "add", "origin", remoteDir).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "push", "origin", "dev").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "push", "origin", branch).OK) } func createCleanupWorkspaceRepo(t *testing.T, repoDir, branch string) { t.Helper() - require.True(t, testCore.Process().Run(context.Background(), "git", "init", "-b", "dev", repoDir).OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@test.com").OK) + core.RequireTrue(t, testCore.Process().Run(context.Background(), "git", "init", "-b", "dev", repoDir).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@test.com").OK) - require.True(t, fs.Write(core.JoinPath(repoDir, "README.md"), "# cleanup").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "initial").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(repoDir, "README.md"), "# cleanup").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "initial").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "checkout", "-b", branch).OK) - require.True(t, fs.Write(core.JoinPath(repoDir, "README.md"), "# cleanup branch").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) - require.True(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "branch work").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "checkout", "-b", branch).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(repoDir, "README.md"), "# cleanup branch").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "branch work").OK) } func cleanupRemoteBranchExists(remoteDir, branch string) bool { @@ -205,7 +203,7 @@ func writeCleanupGitRewrite(t *testing.T, remoteRoot string) { configPath := core.JoinPath(t.TempDir(), "gitconfig") configBody := core.Sprintf("[url \"%s\"]\n\tinsteadOf = ssh://git@forge.lthn.ai:2223/\n", core.Concat("file://", remoteRoot, "/")) - require.True(t, fs.Write(configPath, configBody).OK) + core.RequireTrue(t, fs.Write(configPath, configBody).OK) t.Setenv("GIT_CONFIG_GLOBAL", configPath) } @@ -214,6 +212,7 @@ func newCleanupForgeServer(t *testing.T, remoteDir, branch string, deleteStatus state := &cleanupForgeServerState{} deletePath := core.Concat("/api/v1/repos/core/go-io/branches/", url.PathEscape(branch)) + deletePathDecoded := core.Concat("/api/v1/repos/core/go-io/branches/", branch) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { @@ -224,7 +223,7 @@ func newCleanupForgeServer(t *testing.T, remoteDir, branch string, deleteStatus "number": 42, "html_url": "https://forge.test/core/go-io/pulls/42", }))) - case r.Method == http.MethodDelete && r.URL.Path == deletePath: + case r.Method == http.MethodDelete && (r.URL.Path == deletePath || r.URL.Path == deletePathDecoded || r.URL.EscapedPath() == deletePath): state.deleteCalls++ if deleteStatus != http.StatusNoContent { http.Error(w, "delete failed", deleteStatus) diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index 1d2094c6..0492493c 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -8,8 +8,8 @@ package agentic import ( "context" + core "dappco.re/go" "dappco.re/go/agent/pkg/lib" - core "dappco.re/go/core" "gopkg.in/yaml.v3" ) diff --git a/pkg/agentic/commands_commit.go b/pkg/agentic/commands_commit.go index d1061116..09a63f28 100644 --- a/pkg/agentic/commands_commit.go +++ b/pkg/agentic/commands_commit.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func (s *PrepSubsystem) registerCommitCommands() { c := s.Core() diff --git a/pkg/agentic/commands_commit_test.go b/pkg/agentic/commands_commit_test.go index 30ba7271..cdadebbc 100644 --- a/pkg/agentic/commands_commit_test.go +++ b/pkg/agentic/commands_commit_test.go @@ -5,9 +5,7 @@ package agentic import ( "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsCommit_RegisterCommitCommands_Good(t *testing.T) { @@ -16,8 +14,8 @@ func TestCommandsCommit_RegisterCommitCommands_Good(t *testing.T) { s.registerCommitCommands() - assert.Contains(t, c.Commands(), "commit") - assert.Contains(t, c.Commands(), "agentic:commit") + core.AssertContains(t, c.Commands(), "commit") + core.AssertContains(t, c.Commands(), "agentic:commit") } func TestCommandsCommit_CmdCommit_Good(t *testing.T) { @@ -26,8 +24,8 @@ func TestCommandsCommit_CmdCommit_Good(t *testing.T) { workspaceName := "core/go-io/task-42" workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) - require.True(t, fs.EnsureDir(workspaceDir).OK) - require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "completed", Agent: "codex", Repo: "go-io", @@ -40,29 +38,29 @@ func TestCommandsCommit_CmdCommit_Good(t *testing.T) { s := &PrepSubsystem{} output := captureStdout(t, func() { result := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) commitOutput, ok := result.Value.(CommitOutput) - require.True(t, ok) - assert.Equal(t, workspaceName, commitOutput.Workspace) - assert.False(t, commitOutput.Skipped) - assert.NotEmpty(t, commitOutput.JournalPath) - assert.NotEmpty(t, commitOutput.MarkerPath) - assert.NotEmpty(t, commitOutput.CommittedAt) + core.RequireTrue(t, ok) + core.AssertEqual(t, workspaceName, commitOutput.Workspace) + core.AssertFalse(t, commitOutput.Skipped) + core.AssertNotEmpty(t, commitOutput.JournalPath) + core.AssertNotEmpty(t, commitOutput.MarkerPath) + core.AssertNotEmpty(t, commitOutput.CommittedAt) }) - assert.Contains(t, output, "workspace: core/go-io/task-42") - assert.Contains(t, output, "journal:") - assert.Contains(t, output, "committed:") + core.AssertContains(t, output, "workspace: core/go-io/task-42") + core.AssertContains(t, output, "journal:") + core.AssertContains(t, output, "committed:") } func TestCommandsCommit_CmdCommit_Bad_MissingWorkspace(t *testing.T) { s := &PrepSubsystem{} result := s.cmdCommit(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "workspace is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "workspace is required") } func TestCommandsCommit_CmdCommit_Ugly_MissingStatus(t *testing.T) { @@ -71,14 +69,14 @@ func TestCommandsCommit_CmdCommit_Ugly_MissingStatus(t *testing.T) { workspaceName := "core/go-io/task-99" workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) - require.True(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) s := &PrepSubsystem{} result := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "status not found") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "status not found") } func TestCommandsCommit_CmdCommit_Ugly_Idempotent(t *testing.T) { @@ -87,8 +85,8 @@ func TestCommandsCommit_CmdCommit_Ugly_Idempotent(t *testing.T) { workspaceName := "core/go-io/task-100" workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) - require.True(t, fs.EnsureDir(workspaceDir).OK) - require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "merged", Agent: "codex", Repo: "go-io", @@ -100,13 +98,13 @@ func TestCommandsCommit_CmdCommit_Ugly_Idempotent(t *testing.T) { s := &PrepSubsystem{} first := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) - require.True(t, first.OK) + core.RequireTrue(t, first.OK) second := s.cmdCommit(core.NewOptions(core.Option{Key: "_arg", Value: workspaceName})) - require.True(t, second.OK) + core.RequireTrue(t, second.OK) commitOutput, ok := second.Value.(CommitOutput) - require.True(t, ok) - assert.True(t, commitOutput.Skipped) - assert.NotEmpty(t, commitOutput.MarkerPath) + core.RequireTrue(t, ok) + core.AssertTrue(t, commitOutput.Skipped) + core.AssertNotEmpty(t, commitOutput.MarkerPath) } diff --git a/pkg/agentic/commands_core.go b/pkg/agentic/commands_core.go index 37eabe6c..5f0be208 100644 --- a/pkg/agentic/commands_core.go +++ b/pkg/agentic/commands_core.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) // Follow-up: docs/RFC.pipeline.md still does not exist in this checkout. diff --git a/pkg/agentic/commands_core_test.go b/pkg/agentic/commands_core_test.go index f03f3ae5..4fed56aa 100644 --- a/pkg/agentic/commands_core_test.go +++ b/pkg/agentic/commands_core_test.go @@ -5,9 +5,7 @@ package agentic import ( "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsCore_RegisterCoreCommands_Good(t *testing.T) { @@ -16,15 +14,15 @@ func TestCommandsCore_RegisterCoreCommands_Good(t *testing.T) { s.registerCoreCommands() for _, spec := range coreCommandSpecs { - assert.Contains(t, c.Commands(), spec.Path) + core.AssertContains(t, c.Commands(), spec.Path) result := c.Command(spec.Path) - require.True(t, result.OK, spec.Path) + core.RequireTrue(t, result.OK, spec.Path) command, ok := result.Value.(*core.Command) - require.True(t, ok, spec.Path) - assert.Equal(t, spec.Description, command.Description) - assert.NotEmpty(t, command.Description) + core.RequireTrue(t, ok, spec.Path) + core.AssertEqual(t, spec.Description, command.Description) + core.AssertNotEmpty(t, command.Description) } } @@ -38,14 +36,14 @@ func TestCommandsCore_CliHelp_Good_ListsAllSubcommands(t *testing.T) { result = c.Cli().Run("core", "--help") }) - require.True(t, result.OK) - assert.Contains(t, output, "usage: core [pipeline] [--help]") + core.RequireTrue(t, result.OK) + core.AssertContains(t, output, "usage: core [pipeline] [--help]") for _, spec := range coreCommandSpecs { if spec.Path == "core" { continue } - assert.Contains(t, output, spec.Usage) + core.AssertContains(t, output, spec.Usage) } } @@ -59,12 +57,12 @@ func TestCommandsCore_CliRoute_Bad_AuditPlaceholder(t *testing.T) { result = c.Cli().Run("core", "pipeline", "audit", "go-io") }) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "core pipeline audit is not yet implemented") - assert.Contains(t, output, "usage: core pipeline audit [--help]") - assert.Contains(t, output, "about: Stage 1: audit issues into implementation work") - assert.Contains(t, output, "status: not yet implemented") - assert.Contains(t, output, "docs/flow/RFC.flow-audit-issues.md") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "core pipeline audit is not yet implemented") + core.AssertContains(t, output, "usage: core pipeline audit [--help]") + core.AssertContains(t, output, "about: Stage 1: audit issues into implementation work") + core.AssertContains(t, output, "status: not yet implemented") + core.AssertContains(t, output, "docs/flow/RFC.flow-audit-issues.md") } diff --git a/pkg/agentic/commands_example_test.go b/pkg/agentic/commands_example_test.go index 863265be..1f48ae03 100644 --- a/pkg/agentic/commands_example_test.go +++ b/pkg/agentic/commands_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func Example_parseIntString() { core.Println(parseIntString("42")) diff --git a/pkg/agentic/commands_flow_test.go b/pkg/agentic/commands_flow_test.go index 2741ddf5..a2fd2043 100644 --- a/pkg/agentic/commands_flow_test.go +++ b/pkg/agentic/commands_flow_test.go @@ -5,10 +5,9 @@ package agentic import ( "os" "testing" + "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func newFlowCommandPrep() (*PrepSubsystem, *core.Core) { @@ -23,10 +22,10 @@ func newFlowCommandPrep() (*PrepSubsystem, *core.Core) { func TestCommandsFlow_CmdFlowPreview_Good_ReadsYamlFlowFile(t *testing.T) { dir := t.TempDir() flowPath := core.JoinPath(dir, "pkg", "lib", "flow", "verify") - require.True(t, fs.EnsureDir(flowPath).OK) + core.RequireTrue(t, fs.EnsureDir(flowPath).OK) filePath := core.JoinPath(flowPath, "go-qa.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Go QA\n", "description: Build and test a Go project\n", "steps:\n", @@ -39,26 +38,26 @@ func TestCommandsFlow_CmdFlowPreview_Good_ReadsYamlFlowFile(t *testing.T) { s := newTestPrep(t) output := captureStdout(t, func() { r := s.cmdFlowPreview(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.True(t, flowOutput.Success) - assert.Equal(t, filePath, flowOutput.Source) - assert.Equal(t, "Go QA", flowOutput.Name) - assert.Equal(t, "Build and test a Go project", flowOutput.Description) - assert.Equal(t, 2, flowOutput.Steps) + core.RequireTrue(t, ok) + core.AssertTrue(t, flowOutput.Success) + core.AssertEqual(t, filePath, flowOutput.Source) + core.AssertEqual(t, "Go QA", flowOutput.Name) + core.AssertEqual(t, "Build and test a Go project", flowOutput.Description) + core.AssertEqual(t, 2, flowOutput.Steps) }) - assert.Contains(t, output, "steps: 2") - assert.Contains(t, output, "build: run go build ./...") - assert.Contains(t, output, "verify: flow verify/go-qa.yaml") + core.AssertContains(t, output, "steps: 2") + core.AssertContains(t, output, "build: run go build ./...") + core.AssertContains(t, output, "verify: flow verify/go-qa.yaml") } func TestCommandsFlow_CmdRunFlow_Good_ExecutesSequentialSteps(t *testing.T) { dir := t.TempDir() filePath := core.JoinPath(dir, "execute-flow.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Execute Flow\n", "description: Run registered commands\n", "steps:\n", @@ -76,57 +75,57 @@ func TestCommandsFlow_CmdRunFlow_Good_ExecutesSequentialSteps(t *testing.T) { s, c := newFlowCommandPrep() invoked := []string{} - require.True(t, c.Command("flow/test-first", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/test-first", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "first") core.Print(nil, "first stdout") return core.Result{OK: true} }}).OK) - require.True(t, c.Command("flow/test-second", core.Command{Action: func(options core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/test-second", core.Command{Action: func(options core.Options) core.Result { invoked = append(invoked, "second") - assert.Equal(t, "fast", options.String("mode")) + core.AssertEqual(t, "fast", options.String("mode")) _, _ = os.Stderr.WriteString("second stderr\n") return core.Result{OK: true} }}).OK) - require.True(t, c.Command("flow/test-third", core.Command{Action: func(options core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/test-third", core.Command{Action: func(options core.Options) core.Result { invoked = append(invoked, "third") - assert.Equal(t, "payload", options.String("_arg")) + core.AssertEqual(t, "payload", options.String("_arg")) return core.Result{OK: true} }}).OK) output := captureStdout(t, func() { r := s.cmdRunFlow(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.True(t, flowOutput.Success) - assert.Equal(t, filePath, flowOutput.Source) - assert.Equal(t, "Execute Flow", flowOutput.Name) - assert.Equal(t, "Run registered commands", flowOutput.Description) - assert.Equal(t, 3, flowOutput.Steps) - assert.Equal(t, 3, flowOutput.Executed) - assert.Equal(t, 3, flowOutput.Passed) - assert.Equal(t, 0, flowOutput.Failed) - require.Len(t, flowOutput.StepResults, 3) - assert.Equal(t, "first", flowOutput.StepResults[0].Name) - assert.Equal(t, "flow/test-second", flowOutput.StepResults[1].Command) - assert.Equal(t, "second stderr\n", flowOutput.StepResults[1].Stderr) + core.RequireTrue(t, ok) + core.AssertTrue(t, flowOutput.Success) + core.AssertEqual(t, filePath, flowOutput.Source) + core.AssertEqual(t, "Execute Flow", flowOutput.Name) + core.AssertEqual(t, "Run registered commands", flowOutput.Description) + core.AssertEqual(t, 3, flowOutput.Steps) + core.AssertEqual(t, 3, flowOutput.Executed) + core.AssertEqual(t, 3, flowOutput.Passed) + core.AssertEqual(t, 0, flowOutput.Failed) + core.AssertLen(t, flowOutput.StepResults, 3) + core.AssertEqual(t, "first", flowOutput.StepResults[0].Name) + core.AssertEqual(t, "flow/test-second", flowOutput.StepResults[1].Command) + core.AssertEqual(t, "second stderr\n", flowOutput.StepResults[1].Stderr) }) - assert.Equal(t, []string{"first", "second", "third"}, invoked) - assert.Contains(t, output, "steps: 3") - assert.Contains(t, output, "first stdout") - assert.Contains(t, output, "second stderr") - assert.Contains(t, output, "totals: ran=3 passed=3 failed=0") + core.AssertEqual(t, []string{"first", "second", "third"}, invoked) + core.AssertContains(t, output, "steps: 3") + core.AssertContains(t, output, "first stdout") + core.AssertContains(t, output, "second stderr") + core.AssertContains(t, output, "totals: ran=3 passed=3 failed=0") } func TestCommandsFlow_CmdRunFlow_Good_RendersVariablesAndDryRun(t *testing.T) { dir := t.TempDir() flowPath := core.JoinPath(dir, "pkg", "lib", "flow", "verify") - require.True(t, fs.EnsureDir(flowPath).OK) + core.RequireTrue(t, fs.EnsureDir(flowPath).OK) filePath := core.JoinPath(flowPath, "go-qa.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Go QA\n", "description: Build {{ repo }}\n", "steps:\n", @@ -143,29 +142,29 @@ func TestCommandsFlow_CmdRunFlow_Good_RendersVariablesAndDryRun(t *testing.T) { core.Option{Key: "dry-run", Value: true}, core.Option{Key: "var", Value: "repo=core/go"}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.True(t, flowOutput.Success) - assert.Equal(t, "Go QA", flowOutput.Name) - assert.Equal(t, "Build core/go", flowOutput.Description) - assert.Equal(t, 1, flowOutput.Steps) + core.RequireTrue(t, ok) + core.AssertTrue(t, flowOutput.Success) + core.AssertEqual(t, "Go QA", flowOutput.Name) + core.AssertEqual(t, "Build core/go", flowOutput.Description) + core.AssertEqual(t, 1, flowOutput.Steps) }) - assert.Contains(t, output, "dry-run: true") - assert.Contains(t, output, "vars: 1") - assert.Contains(t, output, "desc: Build core/go") - assert.Contains(t, output, "build: cmd flow/test-build --repo=core/go") + core.AssertContains(t, output, "dry-run: true") + core.AssertContains(t, output, "vars: 1") + core.AssertContains(t, output, "desc: Build core/go") + core.AssertContains(t, output, "build: cmd flow/test-build --repo=core/go") } func TestCommandsFlow_CmdFlowPreview_Good_ResolvesNestedFlowReferences(t *testing.T) { dir := t.TempDir() flowRoot := core.JoinPath(dir, "pkg", "lib", "flow") - require.True(t, fs.EnsureDir(core.JoinPath(flowRoot, "verify")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(flowRoot, "verify")).OK) rootPath := core.JoinPath(flowRoot, "root.yaml") - require.True(t, fs.Write(rootPath, core.Concat( + core.RequireTrue(t, fs.Write(rootPath, core.Concat( "name: Root Flow\n", "description: Resolve nested flow references\n", "steps:\n", @@ -174,7 +173,7 @@ func TestCommandsFlow_CmdFlowPreview_Good_ResolvesNestedFlowReferences(t *testin )).OK) childPath := core.JoinPath(flowRoot, "verify", "go-qa.yaml") - require.True(t, fs.Write(childPath, core.Concat( + core.RequireTrue(t, fs.Write(childPath, core.Concat( "name: Child Flow\n", "description: Nested flow body\n", "steps:\n", @@ -185,34 +184,34 @@ func TestCommandsFlow_CmdFlowPreview_Good_ResolvesNestedFlowReferences(t *testin s := newTestPrep(t) output := captureStdout(t, func() { r := s.cmdFlowPreview(core.NewOptions(core.Option{Key: "_arg", Value: rootPath})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.True(t, flowOutput.Success) - assert.Equal(t, 1, flowOutput.Steps) - assert.Equal(t, 2, flowOutput.ResolvedSteps) + core.RequireTrue(t, ok) + core.AssertTrue(t, flowOutput.Success) + core.AssertEqual(t, 1, flowOutput.Steps) + core.AssertEqual(t, 2, flowOutput.ResolvedSteps) }) - assert.Contains(t, output, "resolved:") - assert.Contains(t, output, "child-run: run echo child") + core.AssertContains(t, output, "resolved:") + core.AssertContains(t, output, "child-run: run echo child") } func TestCommandsFlow_CmdRunFlow_Bad_MissingPath(t *testing.T) { s := newTestPrep(t) r := s.cmdRunFlow(core.NewOptions()) - require.False(t, r.OK) + core.AssertFalse(t, r.OK) err, ok := r.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "flow path or slug is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "flow path or slug is required") } func TestCommandsFlow_CmdRunFlow_Bad_RejectsUnknownCommandAtParseTime(t *testing.T) { dir := t.TempDir() filePath := core.JoinPath(dir, "missing-command.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Missing Command\n", "steps:\n", " - name: first\n", @@ -223,38 +222,38 @@ func TestCommandsFlow_CmdRunFlow_Bad_RejectsUnknownCommandAtParseTime(t *testing s, c := newFlowCommandPrep() invoked := []string{} - require.True(t, c.Command("flow/known", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/known", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "known") return core.Result{OK: true} }}).OK) r := s.cmdRunFlow(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.False(t, r.OK) + core.AssertFalse(t, r.OK) err, ok := r.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "references unknown command: flow/missing") - assert.Empty(t, invoked) + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "references unknown command: flow/missing") + core.AssertEmpty(t, invoked) } func TestCommandsFlow_CmdRunFlow_Ugly_InvalidYaml(t *testing.T) { dir := t.TempDir() filePath := core.JoinPath(dir, "broken-flow.yaml") - require.True(t, fs.Write(filePath, "name: [broken\n").OK) + core.RequireTrue(t, fs.Write(filePath, "name: [broken\n").OK) s := newTestPrep(t) r := s.cmdRunFlow(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.False(t, r.OK) + core.AssertFalse(t, r.OK) err, ok := r.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "invalid flow definition") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "invalid flow definition") } func TestCommandsFlow_CmdRunFlow_Ugly_StopsOnFirstFailure(t *testing.T) { dir := t.TempDir() filePath := core.JoinPath(dir, "stops-on-failure.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Stop On Failure\n", "steps:\n", " - name: first\n", @@ -267,43 +266,43 @@ func TestCommandsFlow_CmdRunFlow_Ugly_StopsOnFirstFailure(t *testing.T) { s, c := newFlowCommandPrep() invoked := []string{} - require.True(t, c.Command("flow/first", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/first", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "first") return core.Result{OK: true} }}).OK) - require.True(t, c.Command("flow/fail", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/fail", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "second") return core.Result{Value: "boom", OK: false} }}).OK) - require.True(t, c.Command("flow/third", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/third", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "third") return core.Result{OK: true} }}).OK) output := captureStdout(t, func() { r := s.cmdRunFlow(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.False(t, r.OK) + core.AssertFalse(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.False(t, flowOutput.Success) - assert.Equal(t, 2, flowOutput.Executed) - assert.Equal(t, 1, flowOutput.Passed) - assert.Equal(t, 1, flowOutput.Failed) - require.Len(t, flowOutput.StepResults, 2) - assert.Equal(t, "boom", flowOutput.StepResults[1].Error) + core.RequireTrue(t, ok) + core.AssertFalse(t, flowOutput.Success) + core.AssertEqual(t, 2, flowOutput.Executed) + core.AssertEqual(t, 1, flowOutput.Passed) + core.AssertEqual(t, 1, flowOutput.Failed) + core.AssertLen(t, flowOutput.StepResults, 2) + core.AssertContains(t, flowOutput.StepResults[1].Error, "boom") }) - assert.Equal(t, []string{"first", "second"}, invoked) - assert.Contains(t, output, "second: failed") - assert.Contains(t, output, "totals: ran=2 passed=1 failed=1") - assert.NotContains(t, output, "third: passed") + core.AssertEqual(t, []string{"first", "second"}, invoked) + core.AssertContains(t, output, "second: failed") + core.AssertContains(t, output, "totals: ran=2 passed=1 failed=1") + core.AssertNotContains(t, output, "third: passed") } func TestCommandsFlow_CmdRunFlow_Ugly_ContinueOnErrorRunsRemainingSteps(t *testing.T) { dir := t.TempDir() filePath := core.JoinPath(dir, "continue-on-error.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Continue On Error\n", "steps:\n", " - name: first\n", @@ -317,44 +316,44 @@ func TestCommandsFlow_CmdRunFlow_Ugly_ContinueOnErrorRunsRemainingSteps(t *testi s, c := newFlowCommandPrep() invoked := []string{} - require.True(t, c.Command("flow/first", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/first", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "first") return core.Result{OK: true} }}).OK) - require.True(t, c.Command("flow/fail", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/fail", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "second") return core.Result{Value: "boom", OK: false} }}).OK) - require.True(t, c.Command("flow/third", core.Command{Action: func(_ core.Options) core.Result { + core.RequireTrue(t, c.Command("flow/third", core.Command{Action: func(_ core.Options) core.Result { invoked = append(invoked, "third") return core.Result{OK: true} }}).OK) output := captureStdout(t, func() { r := s.cmdRunFlow(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.True(t, flowOutput.Success) - assert.Equal(t, 3, flowOutput.Executed) - assert.Equal(t, 2, flowOutput.Passed) - assert.Equal(t, 1, flowOutput.Failed) - require.Len(t, flowOutput.StepResults, 3) - assert.True(t, flowOutput.StepResults[1].ContinueOnError) - assert.Equal(t, "boom", flowOutput.StepResults[1].Error) + core.RequireTrue(t, ok) + core.AssertTrue(t, flowOutput.Success) + core.AssertEqual(t, 3, flowOutput.Executed) + core.AssertEqual(t, 2, flowOutput.Passed) + core.AssertEqual(t, 1, flowOutput.Failed) + core.AssertLen(t, flowOutput.StepResults, 3) + core.AssertTrue(t, flowOutput.StepResults[1].ContinueOnError) + core.AssertContains(t, flowOutput.StepResults[1].Error, "boom") }) - assert.Equal(t, []string{"first", "second", "third"}, invoked) - assert.Contains(t, output, "second: failed (continued)") - assert.Contains(t, output, "third: passed") - assert.Contains(t, output, "totals: ran=3 passed=2 failed=1") + core.AssertEqual(t, []string{"first", "second", "third"}, invoked) + core.AssertContains(t, output, "second: failed (continued)") + core.AssertContains(t, output, "third: passed") + core.AssertContains(t, output, "totals: ran=3 passed=2 failed=1") } func TestCommandsFlow_CmdRunFlow_Good_EmptyFlowIsNoop(t *testing.T) { dir := t.TempDir() filePath := core.JoinPath(dir, "empty-flow.yaml") - require.True(t, fs.Write(filePath, core.Concat( + core.RequireTrue(t, fs.Write(filePath, core.Concat( "name: Empty Flow\n", "steps: []\n", )).OK) @@ -362,20 +361,20 @@ func TestCommandsFlow_CmdRunFlow_Good_EmptyFlowIsNoop(t *testing.T) { s, _ := newFlowCommandPrep() output := captureStdout(t, func() { r := s.cmdRunFlow(core.NewOptions(core.Option{Key: "_arg", Value: filePath})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) flowOutput, ok := r.Value.(FlowRunOutput) - require.True(t, ok) - assert.True(t, flowOutput.Success) - assert.Equal(t, 0, flowOutput.Steps) - assert.Equal(t, 0, flowOutput.Executed) - assert.Equal(t, 0, flowOutput.Passed) - assert.Equal(t, 0, flowOutput.Failed) - assert.Empty(t, flowOutput.StepResults) + core.RequireTrue(t, ok) + core.AssertTrue(t, flowOutput.Success) + core.AssertEqual(t, 0, flowOutput.Steps) + core.AssertEqual(t, 0, flowOutput.Executed) + core.AssertEqual(t, 0, flowOutput.Passed) + core.AssertEqual(t, 0, flowOutput.Failed) + core.AssertEmpty(t, flowOutput.StepResults) }) - assert.Contains(t, output, "steps: 0") - assert.Contains(t, output, "totals: ran=0 passed=0 failed=0") + core.AssertContains(t, output, "steps: 0") + core.AssertContains(t, output, "totals: ran=0 passed=0 failed=0") } func TestCommandsFlow_CmdFlowPreview_Good_VariablesAlias(t *testing.T) { @@ -404,11 +403,11 @@ func TestCommandsFlow_CmdFlowPreview_Good_VariablesAlias(t *testing.T) { "VALUE": "ok", }}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "name: release deployment") - assert.Contains(t, output, "1. lint") + core.AssertContains(t, output, "name: release deployment") + core.AssertContains(t, output, "1. lint") } func TestCommandsFlow_CmdFlowPreview_Bad_MissingPath(t *testing.T) { @@ -419,7 +418,7 @@ func TestCommandsFlow_CmdFlowPreview_Bad_MissingPath(t *testing.T) { } r := s.cmdFlowPreview(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsFlow_CmdFlowPreview_Ugly_InvalidYaml(t *testing.T) { @@ -434,5 +433,5 @@ func TestCommandsFlow_CmdFlowPreview_Ugly_InvalidYaml(t *testing.T) { } r := s.cmdFlowPreview(core.NewOptions(core.Option{Key: "_arg", Value: flowPath})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } diff --git a/pkg/agentic/commands_forge.go b/pkg/agentic/commands_forge.go index f4c582e6..3f20fc93 100644 --- a/pkg/agentic/commands_forge.go +++ b/pkg/agentic/commands_forge.go @@ -6,7 +6,7 @@ import ( "context" "strconv" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/forge" forge_types "dappco.re/go/forge/types" ) @@ -239,15 +239,19 @@ func (s *PrepSubsystem) cmdIssueCreate(options core.Options) core.Result { labelNames := core.Split(labels, ",") allLabels, err := s.forge.Labels.ListRepoLabels(ctx, org, repo) if err == nil { + labelIDs := []int64{} for _, name := range labelNames { name = core.Trim(name) for _, l := range allLabels { if l.Name == name { - createOptions.Labels = append(createOptions.Labels, l.ID) + labelIDs = append(labelIDs, l.ID) break } } } + if len(labelIDs) > 0 { + createOptions.Labels = labelIDs + } } } diff --git a/pkg/agentic/commands_forge_example_test.go b/pkg/agentic/commands_forge_example_test.go index 1dd9a9e1..e6d887c2 100644 --- a/pkg/agentic/commands_forge_example_test.go +++ b/pkg/agentic/commands_forge_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func Example_parseForgeArgs() { org, repo, num := parseForgeArgs(core.NewOptions( diff --git a/pkg/agentic/commands_forge_test.go b/pkg/agentic/commands_forge_test.go index bad742f8..cbb24aa3 100644 --- a/pkg/agentic/commands_forge_test.go +++ b/pkg/agentic/commands_forge_test.go @@ -8,9 +8,7 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) // --- parseForgeArgs --- @@ -22,9 +20,9 @@ func TestCommandsforge_ParseForgeArgs_Good_AllFields(t *testing.T) { core.Option{Key: "number", Value: "42"}, ) org, repo, num := parseForgeArgs(opts) - assert.Equal(t, "myorg", org) - assert.Equal(t, "myrepo", repo) - assert.Equal(t, int64(42), num) + core.AssertEqual(t, "myorg", org) + core.AssertEqual(t, "myrepo", repo) + core.AssertEqual(t, int64(42), num) } func TestCommandsforge_ParseForgeArgs_Good_DefaultOrg(t *testing.T) { @@ -32,17 +30,17 @@ func TestCommandsforge_ParseForgeArgs_Good_DefaultOrg(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, ) org, repo, num := parseForgeArgs(opts) - assert.Equal(t, "core", org, "should default to 'core'") - assert.Equal(t, "go-io", repo) - assert.Equal(t, int64(0), num, "no number provided") + core.AssertEqual(t, "core", org, "should default to 'core'") + core.AssertEqual(t, "go-io", repo) + core.AssertEqual(t, int64(0), num, "no number provided") } func TestCommandsforge_ParseForgeArgs_Bad_EmptyOpts(t *testing.T) { opts := core.NewOptions() org, repo, num := parseForgeArgs(opts) - assert.Equal(t, "core", org, "should default to 'core'") - assert.Empty(t, repo) - assert.Equal(t, int64(0), num) + core.AssertEqual(t, "core", org, "should default to 'core'") + core.AssertEmpty(t, repo) + core.AssertEqual(t, int64(0), num) } func TestCommandsforge_ParseForgeArgs_Bad_InvalidNumber(t *testing.T) { @@ -51,16 +49,16 @@ func TestCommandsforge_ParseForgeArgs_Bad_InvalidNumber(t *testing.T) { core.Option{Key: "number", Value: "not-a-number"}, ) _, _, num := parseForgeArgs(opts) - assert.Equal(t, int64(0), num, "invalid number should parse as 0") + core.AssertEqual(t, int64(0), num, "invalid number should parse as 0") } // --- formatIndex --- func TestCommandsforge_FormatIndex_Good(t *testing.T) { - assert.Equal(t, "1", formatIndex(1)) - assert.Equal(t, "42", formatIndex(42)) - assert.Equal(t, "0", formatIndex(0)) - assert.Equal(t, "999999", formatIndex(999999)) + core.AssertEqual(t, "1", formatIndex(1)) + core.AssertEqual(t, "42", formatIndex(42)) + core.AssertEqual(t, "0", formatIndex(0)) + core.AssertEqual(t, "999999", formatIndex(999999)) } // --- parseForgeArgs Ugly --- @@ -70,9 +68,9 @@ func TestCommandsforge_ParseForgeArgs_Ugly_OrgSetButNoRepo(t *testing.T) { core.Option{Key: "org", Value: "custom-org"}, ) org, repo, num := parseForgeArgs(opts) - assert.Equal(t, "custom-org", org) - assert.Empty(t, repo, "repo should be empty when only org is set") - assert.Equal(t, int64(0), num) + core.AssertEqual(t, "custom-org", org) + core.AssertEmpty(t, repo, "repo should be empty when only org is set") + core.AssertEqual(t, int64(0), num) } func TestCommandsforge_ParseForgeArgs_Ugly_NegativeNumber(t *testing.T) { @@ -81,7 +79,7 @@ func TestCommandsforge_ParseForgeArgs_Ugly_NegativeNumber(t *testing.T) { core.Option{Key: "number", Value: "-5"}, ) _, _, num := parseForgeArgs(opts) - assert.Equal(t, int64(-5), num, "negative numbers parse but are semantically invalid") + core.AssertEqual(t, int64(-5), num, "negative numbers parse but are semantically invalid") } func TestCommandsforge_ParseForgeArgs_Ugly_InvalidNames(t *testing.T) { @@ -90,27 +88,29 @@ func TestCommandsforge_ParseForgeArgs_Ugly_InvalidNames(t *testing.T) { core.Option{Key: "_arg", Value: "repo/with/slashes"}, ) org, repo, num := parseForgeArgs(opts) - assert.Empty(t, org) - assert.Empty(t, repo) - assert.Equal(t, int64(0), num) + core.AssertEmpty(t, org) + core.AssertEmpty(t, repo) + core.AssertEqual(t, int64(0), num) } // --- formatIndex Bad/Ugly --- func TestCommandsforge_FormatIndex_Bad_Negative(t *testing.T) { result := formatIndex(-1) - assert.Equal(t, "-1", result, "negative should format as negative string") + core.AssertEqual(t, "-1", result, "negative should format as negative string") + core.AssertContains(t, result, "-") } func TestCommandsforge_FormatIndex_Ugly_VeryLarge(t *testing.T) { result := formatIndex(9999999999) - assert.Equal(t, "9999999999", result) + core.AssertEqual(t, "9999999999", result) + core.AssertLen(t, result, 10) } func TestCommandsforge_FormatIndex_Ugly_MaxInt64(t *testing.T) { result := formatIndex(9223372036854775807) // math.MaxInt64 - assert.NotEmpty(t, result) - assert.Equal(t, "9223372036854775807", result) + core.AssertNotEmpty(t, result) + core.AssertEqual(t, "9223372036854775807", result) } // --- Forge commands Ugly (special chars → API returns 404/error) --- @@ -123,7 +123,7 @@ func TestCommandsforge_CmdIssueGet_Ugly(t *testing.T) { core.Option{Key: "_arg", Value: "go-io/"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdRepoSync_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdRepoSync(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdRepoSync_Good_ResetLocalRepo(t *testing.T) { @@ -380,7 +380,7 @@ func TestCommandsforge_CmdRepoSync_Good_ResetLocalRepo(t *testing.T) { logPath := core.JoinPath(t.TempDir(), "git.log") gitPath := core.JoinPath(binDir, "git") fs.Write(gitPath, core.Concat("#!/bin/sh\nprintf '%s\\n' \"$*\" >> ", logPath, "\nexit 0\n")) - assert.True(t, testCore.Process().RunIn(context.Background(), binDir, "chmod", "+x", gitPath).OK) + core.AssertTrue(t, testCore.Process().RunIn(context.Background(), binDir, "chmod", "+x", gitPath).OK) oldPath := core.Env("PATH") t.Setenv("PATH", core.Concat(binDir, ":", oldPath)) @@ -396,49 +396,49 @@ func TestCommandsforge_CmdRepoSync_Good_ResetLocalRepo(t *testing.T) { core.Option{Key: "branch", Value: "main"}, core.Option{Key: "reset", Value: true}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "fetched core/test-repo@main") - assert.Contains(t, output, "reset") + core.AssertContains(t, output, "fetched core/test-repo@main") + core.AssertContains(t, output, "reset") logResult := fs.Read(logPath) - assert.True(t, logResult.OK) - assert.Contains(t, logResult.Value.(string), "fetch origin") - assert.Contains(t, logResult.Value.(string), "reset --hard origin/main") + core.AssertTrue(t, logResult.OK) + core.AssertContains(t, logResult.Value.(string), "fetch origin") + core.AssertContains(t, logResult.Value.(string), "reset --hard origin/main") } func TestCommandsforge_RegisterForgeCommands_Good_RepoSyncRegistered(t *testing.T) { s, c := testPrepWithCore(t, nil) s.registerForgeCommands() - assert.Contains(t, c.Commands(), "repo/sync") - assert.Contains(t, c.Commands(), "agentic:repo/sync") - assert.Contains(t, c.Commands(), "issue/get") - assert.Contains(t, c.Commands(), "agentic:issue/get") - assert.Contains(t, c.Commands(), "issue/list") - assert.Contains(t, c.Commands(), "agentic:issue/list") - assert.Contains(t, c.Commands(), "issue/comment") - assert.Contains(t, c.Commands(), "agentic:issue/comment") - assert.Contains(t, c.Commands(), "issue/create") - assert.Contains(t, c.Commands(), "agentic:issue/create") - assert.Contains(t, c.Commands(), "issue/assign") - assert.Contains(t, c.Commands(), "agentic:issue/assign") - assert.Contains(t, c.Commands(), "issue/report") - assert.Contains(t, c.Commands(), "agentic:issue/report") - assert.Contains(t, c.Commands(), "issue/update") - assert.Contains(t, c.Commands(), "agentic:issue/update") - assert.Contains(t, c.Commands(), "issue/archive") - assert.Contains(t, c.Commands(), "agentic:issue/archive") - assert.Contains(t, c.Commands(), "pr/get") - assert.Contains(t, c.Commands(), "agentic:pr/get") - assert.Contains(t, c.Commands(), "pr/list") - assert.Contains(t, c.Commands(), "agentic:pr/list") - assert.Contains(t, c.Commands(), "pr/merge") - assert.Contains(t, c.Commands(), "agentic:pr/merge") - assert.Contains(t, c.Commands(), "pr/close") - assert.Contains(t, c.Commands(), "agentic:pr/close") - assert.Contains(t, c.Commands(), "repo/get") - assert.Contains(t, c.Commands(), "agentic:repo/get") - assert.Contains(t, c.Commands(), "repo/list") - assert.Contains(t, c.Commands(), "agentic:repo/list") + core.AssertContains(t, c.Commands(), "repo/sync") + core.AssertContains(t, c.Commands(), "agentic:repo/sync") + core.AssertContains(t, c.Commands(), "issue/get") + core.AssertContains(t, c.Commands(), "agentic:issue/get") + core.AssertContains(t, c.Commands(), "issue/list") + core.AssertContains(t, c.Commands(), "agentic:issue/list") + core.AssertContains(t, c.Commands(), "issue/comment") + core.AssertContains(t, c.Commands(), "agentic:issue/comment") + core.AssertContains(t, c.Commands(), "issue/create") + core.AssertContains(t, c.Commands(), "agentic:issue/create") + core.AssertContains(t, c.Commands(), "issue/assign") + core.AssertContains(t, c.Commands(), "agentic:issue/assign") + core.AssertContains(t, c.Commands(), "issue/report") + core.AssertContains(t, c.Commands(), "agentic:issue/report") + core.AssertContains(t, c.Commands(), "issue/update") + core.AssertContains(t, c.Commands(), "agentic:issue/update") + core.AssertContains(t, c.Commands(), "issue/archive") + core.AssertContains(t, c.Commands(), "agentic:issue/archive") + core.AssertContains(t, c.Commands(), "pr/get") + core.AssertContains(t, c.Commands(), "agentic:pr/get") + core.AssertContains(t, c.Commands(), "pr/list") + core.AssertContains(t, c.Commands(), "agentic:pr/list") + core.AssertContains(t, c.Commands(), "pr/merge") + core.AssertContains(t, c.Commands(), "agentic:pr/merge") + core.AssertContains(t, c.Commands(), "pr/close") + core.AssertContains(t, c.Commands(), "agentic:pr/close") + core.AssertContains(t, c.Commands(), "repo/get") + core.AssertContains(t, c.Commands(), "agentic:repo/get") + core.AssertContains(t, c.Commands(), "repo/list") + core.AssertContains(t, c.Commands(), "agentic:repo/list") } diff --git a/pkg/agentic/commands_message.go b/pkg/agentic/commands_message.go index 8b90956d..e109aefc 100644 --- a/pkg/agentic/commands_message.go +++ b/pkg/agentic/commands_message.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func (s *PrepSubsystem) cmdMessageSend(options core.Options) core.Result { workspace := optionStringValue(options, "workspace", "_arg") diff --git a/pkg/agentic/commands_phase.go b/pkg/agentic/commands_phase.go index c97f7496..badf9bb1 100644 --- a/pkg/agentic/commands_phase.go +++ b/pkg/agentic/commands_phase.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerPhaseCommands() { diff --git a/pkg/agentic/commands_phase_test.go b/pkg/agentic/commands_phase_test.go index 0651f92c..2e4bb5c2 100644 --- a/pkg/agentic/commands_phase_test.go +++ b/pkg/agentic/commands_phase_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsPhase_RegisterPhaseCommands_Good_AllRegistered(t *testing.T) { @@ -16,18 +14,18 @@ func TestCommandsPhase_RegisterPhaseCommands_Good_AllRegistered(t *testing.T) { s.registerPhaseCommands() cmds := c.Commands() - assert.Contains(t, cmds, "phase") - assert.Contains(t, cmds, "agentic:phase") - assert.Contains(t, cmds, "phase/get") - assert.Contains(t, cmds, "agentic:phase/get") - assert.Contains(t, cmds, "phase/update_status") - assert.Contains(t, cmds, "agentic:phase/update_status") - assert.Contains(t, cmds, "phase/update-status") - assert.Contains(t, cmds, "agentic:phase/update-status") - assert.Contains(t, cmds, "phase/add_checkpoint") - assert.Contains(t, cmds, "agentic:phase/add_checkpoint") - assert.Contains(t, cmds, "phase/add-checkpoint") - assert.Contains(t, cmds, "agentic:phase/add-checkpoint") + core.AssertContains(t, cmds, "phase") + core.AssertContains(t, cmds, "agentic:phase") + core.AssertContains(t, cmds, "phase/get") + core.AssertContains(t, cmds, "agentic:phase/get") + core.AssertContains(t, cmds, "phase/update_status") + core.AssertContains(t, cmds, "agentic:phase/update_status") + core.AssertContains(t, cmds, "phase/update-status") + core.AssertContains(t, cmds, "agentic:phase/update-status") + core.AssertContains(t, cmds, "phase/add_checkpoint") + core.AssertContains(t, cmds, "agentic:phase/add_checkpoint") + core.AssertContains(t, cmds, "phase/add-checkpoint") + core.AssertContains(t, cmds, "agentic:phase/add-checkpoint") } func TestCommandsPhase_CmdPhase_Good_GetUpdateCheckpoint(t *testing.T) { @@ -40,7 +38,7 @@ func TestCommandsPhase_CmdPhase_Good_GetUpdateCheckpoint(t *testing.T) { {Number: 1, Name: "Setup", Status: "pending"}, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) output := captureStdout(t, func() { r := s.cmdPhase(core.NewOptions( @@ -48,11 +46,11 @@ func TestCommandsPhase_CmdPhase_Good_GetUpdateCheckpoint(t *testing.T) { core.Option{Key: "_arg", Value: "phase-command-plan"}, core.Option{Key: "phase", Value: 1}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "phase: 1") - assert.Contains(t, output, "name: Setup") - assert.Contains(t, output, "status: pending") + core.AssertContains(t, output, "phase: 1") + core.AssertContains(t, output, "name: Setup") + core.AssertContains(t, output, "status: pending") output = captureStdout(t, func() { r := s.cmdPhase(core.NewOptions( @@ -61,9 +59,9 @@ func TestCommandsPhase_CmdPhase_Good_GetUpdateCheckpoint(t *testing.T) { core.Option{Key: "phase", Value: 1}, core.Option{Key: "status", Value: "completed"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "status: completed") + core.AssertContains(t, output, "status: completed") output = captureStdout(t, func() { r := s.cmdPhase(core.NewOptions( @@ -72,23 +70,23 @@ func TestCommandsPhase_CmdPhase_Good_GetUpdateCheckpoint(t *testing.T) { core.Option{Key: "phase", Value: 1}, core.Option{Key: "note", Value: "Build passes"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "checkpoints: 1") + core.AssertContains(t, output, "checkpoints: 1") } func TestCommandsPhase_CmdPhase_Bad_MissingActionStillShowsUsage(t *testing.T) { s, _ := testPrepWithCore(t, nil) output := captureStdout(t, func() { r := s.cmdPhase(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "core-agent phase get") + core.AssertContains(t, output, "core-agent phase get") } func TestCommandsPhase_CmdPhase_Ugly_UnknownAction(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPhase(core.NewOptions(core.Option{Key: "action", Value: "explode"})) - require.False(t, r.OK) - assert.Contains(t, r.Value.(error).Error(), "unknown phase command") + core.AssertFalse(t, r.OK) + core.AssertContains(t, r.Value.(error).Error(), "unknown phase command") } diff --git a/pkg/agentic/commands_plan.go b/pkg/agentic/commands_plan.go index f98b0492..f649a5de 100644 --- a/pkg/agentic/commands_plan.go +++ b/pkg/agentic/commands_plan.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerPlanCommands() { diff --git a/pkg/agentic/commands_plan_test.go b/pkg/agentic/commands_plan_test.go index dfde5bed..927731bb 100644 --- a/pkg/agentic/commands_plan_test.go +++ b/pkg/agentic/commands_plan_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsPlan_CmdPlanCheck_Good_CompletePlan(t *testing.T) { @@ -28,20 +26,20 @@ func TestCommandsPlan_CmdPlanCheck_Good_CompletePlan(t *testing.T) { }, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdPlanCheck(core.NewOptions(core.Option{Key: "_arg", Value: plan.Slug})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(PlanCheckOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.True(t, output.Complete) - assert.Empty(t, output.Pending) - assert.Equal(t, plan.Slug, output.Plan.Slug) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertTrue(t, output.Complete) + core.AssertEmpty(t, output.Pending) + core.AssertEqual(t, plan.Slug, output.Plan.Slug) } func TestCommandsPlan_CmdPlanCheck_Bad_MissingSlug(t *testing.T) { @@ -49,9 +47,9 @@ func TestCommandsPlan_CmdPlanCheck_Bad_MissingSlug(t *testing.T) { r := s.cmdPlanCheck(core.NewOptions()) - assert.False(t, r.OK) - require.Error(t, r.Value.(error)) - assert.Contains(t, r.Value.(error).Error(), "slug is required") + core.AssertFalse(t, r.OK) + core.AssertError(t, r.Value.(error)) + core.AssertContains(t, r.Value.(error).Error(), "slug is required") } func TestCommandsPlan_CmdPlanCheck_Ugly_IncompletePhase(t *testing.T) { @@ -73,23 +71,23 @@ func TestCommandsPlan_CmdPlanCheck_Ugly_IncompletePhase(t *testing.T) { }, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdPlanCheck(core.NewOptions( core.Option{Key: "slug", Value: plan.Slug}, core.Option{Key: "phase", Value: 1}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) output, ok := r.Value.(PlanCheckOutput) - require.True(t, ok) - assert.False(t, output.Complete) - assert.Equal(t, 1, output.Phase) - assert.Equal(t, "Setup", output.PhaseName) - assert.Equal(t, []string{"Patch code"}, output.Pending) + core.RequireTrue(t, ok) + core.AssertFalse(t, output.Complete) + core.AssertEqual(t, 1, output.Phase) + core.AssertEqual(t, "Setup", output.PhaseName) + core.AssertEqual(t, []string{"Patch code"}, output.Pending) } func TestCommandsPlan_CmdPlan_Good_RoutesCreate(t *testing.T) { @@ -105,12 +103,12 @@ func TestCommandsPlan_CmdPlan_Good_RoutesCreate(t *testing.T) { core.Option{Key: "objective", Value: "Exercise the root plan router"}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(PlanCreateOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.NotEmpty(t, output.ID) - assert.NotEmpty(t, output.Path) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertNotEmpty(t, output.ID) + core.AssertNotEmpty(t, output.Path) } func TestCommandsPlan_CmdPlan_Good_RoutesStatus(t *testing.T) { @@ -122,21 +120,21 @@ func TestCommandsPlan_CmdPlan_Good_RoutesStatus(t *testing.T) { Title: "Status Route Plan", Description: "Exercise the root plan router status action", }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdPlan(core.NewOptions( core.Option{Key: "action", Value: "status"}, core.Option{Key: "slug", Value: plan.Slug}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(PlanCompatibilityGetOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, plan.Slug, output.Plan.Slug) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, plan.Slug, output.Plan.Slug) } func TestCommandsPlan_CmdPlan_Bad_UnknownAction(t *testing.T) { @@ -146,9 +144,9 @@ func TestCommandsPlan_CmdPlan_Bad_UnknownAction(t *testing.T) { core.Option{Key: "action", Value: "does-not-exist"}, )) - require.False(t, r.OK) - require.Error(t, r.Value.(error)) - assert.Contains(t, r.Value.(error).Error(), "unknown plan command") + core.AssertFalse(t, r.OK) + core.AssertError(t, r.Value.(error)) + core.AssertContains(t, r.Value.(error).Error(), "unknown plan command") } func TestCommandsPlan_CmdPlanUpdate_Good_StatusAndAgent(t *testing.T) { @@ -160,21 +158,21 @@ func TestCommandsPlan_CmdPlanUpdate_Good_StatusAndAgent(t *testing.T) { Title: "Update Command", Objective: "Verify the plan update command", }) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdPlanUpdate(core.NewOptions( core.Option{Key: "_arg", Value: created.ID}, core.Option{Key: "status", Value: "ready"}, core.Option{Key: "agent", Value: "codex"}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(PlanUpdateOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, created.ID, output.Plan.ID) - assert.Equal(t, "ready", output.Plan.Status) - assert.Equal(t, "codex", output.Plan.Agent) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, created.ID, output.Plan.ID) + core.AssertEqual(t, "ready", output.Plan.Status) + core.AssertEqual(t, "codex", output.Plan.Agent) } func TestCommandsPlan_CmdPlanUpdate_Bad_MissingFields(t *testing.T) { @@ -184,9 +182,9 @@ func TestCommandsPlan_CmdPlanUpdate_Bad_MissingFields(t *testing.T) { core.Option{Key: "_arg", Value: "plan-123"}, )) - assert.False(t, r.OK) - require.Error(t, r.Value.(error)) - assert.Contains(t, r.Value.(error).Error(), "at least one update field is required") + core.AssertFalse(t, r.OK) + core.AssertError(t, r.Value.(error)) + core.AssertContains(t, r.Value.(error).Error(), "at least one update field is required") } func TestCommandsPlan_HandlePlanCheck_Good_CompletePlan(t *testing.T) { @@ -206,21 +204,21 @@ func TestCommandsPlan_HandlePlanCheck_Good_CompletePlan(t *testing.T) { }, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.handlePlanCheck(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: plan.Slug}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(PlanCheckOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.True(t, output.Complete) - assert.Equal(t, plan.Slug, output.Plan.Slug) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertTrue(t, output.Complete) + core.AssertEqual(t, plan.Slug, output.Plan.Slug) } func TestCommandsPlan_CmdPlanTemplates_Good(t *testing.T) { @@ -230,12 +228,12 @@ func TestCommandsPlan_CmdPlanTemplates_Good(t *testing.T) { core.Option{Key: "category", Value: "development"}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(TemplateListOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.NotZero(t, output.Total) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + assertNotZero(t, output.Total) } func TestCommandsPlan_CmdPlanTemplates_Ugly_NoMatchingCategory(t *testing.T) { @@ -245,13 +243,13 @@ func TestCommandsPlan_CmdPlanTemplates_Ugly_NoMatchingCategory(t *testing.T) { core.Option{Key: "category", Value: "does-not-exist"}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(TemplateListOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Zero(t, output.Total) - assert.Empty(t, output.Templates) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + assertZero(t, output.Total) + core.AssertEmpty(t, output.Templates) } func TestCommandsPlan_RegisterPlanCommands_Good_SpecAliasRegistered(t *testing.T) { @@ -260,28 +258,28 @@ func TestCommandsPlan_RegisterPlanCommands_Good_SpecAliasRegistered(t *testing.T s.registerPlanCommands() - assert.Contains(t, c.Commands(), "agentic:plan") - assert.Contains(t, c.Commands(), "plan") - assert.Contains(t, c.Commands(), "agentic:plan/templates") - assert.Contains(t, c.Commands(), "plan/templates") - assert.Contains(t, c.Commands(), "agentic:plan/create") - assert.Contains(t, c.Commands(), "agentic:plan/get") - assert.Contains(t, c.Commands(), "plan/get") - assert.Contains(t, c.Commands(), "agentic:plan/list") - assert.Contains(t, c.Commands(), "agentic:plan/read") - assert.Contains(t, c.Commands(), "plan/read") - assert.Contains(t, c.Commands(), "agentic:plan/show") - assert.Contains(t, c.Commands(), "plan/show") - assert.Contains(t, c.Commands(), "agentic:plan/status") - assert.Contains(t, c.Commands(), "plan/update") - assert.Contains(t, c.Commands(), "agentic:plan/update") - assert.Contains(t, c.Commands(), "plan/status") - assert.Contains(t, c.Commands(), "plan/update_status") - assert.Contains(t, c.Commands(), "agentic:plan/update_status") - assert.Contains(t, c.Commands(), "agentic:plan/check") - assert.Contains(t, c.Commands(), "plan/check") - assert.Contains(t, c.Commands(), "agentic:plan/archive") - assert.Contains(t, c.Commands(), "plan/archive") - assert.Contains(t, c.Commands(), "agentic:plan/delete") - assert.Contains(t, c.Commands(), "plan/delete") + core.AssertContains(t, c.Commands(), "agentic:plan") + core.AssertContains(t, c.Commands(), "plan") + core.AssertContains(t, c.Commands(), "agentic:plan/templates") + core.AssertContains(t, c.Commands(), "plan/templates") + core.AssertContains(t, c.Commands(), "agentic:plan/create") + core.AssertContains(t, c.Commands(), "agentic:plan/get") + core.AssertContains(t, c.Commands(), "plan/get") + core.AssertContains(t, c.Commands(), "agentic:plan/list") + core.AssertContains(t, c.Commands(), "agentic:plan/read") + core.AssertContains(t, c.Commands(), "plan/read") + core.AssertContains(t, c.Commands(), "agentic:plan/show") + core.AssertContains(t, c.Commands(), "plan/show") + core.AssertContains(t, c.Commands(), "agentic:plan/status") + core.AssertContains(t, c.Commands(), "plan/update") + core.AssertContains(t, c.Commands(), "agentic:plan/update") + core.AssertContains(t, c.Commands(), "plan/status") + core.AssertContains(t, c.Commands(), "plan/update_status") + core.AssertContains(t, c.Commands(), "agentic:plan/update_status") + core.AssertContains(t, c.Commands(), "agentic:plan/check") + core.AssertContains(t, c.Commands(), "plan/check") + core.AssertContains(t, c.Commands(), "agentic:plan/archive") + core.AssertContains(t, c.Commands(), "plan/archive") + core.AssertContains(t, c.Commands(), "agentic:plan/delete") + core.AssertContains(t, c.Commands(), "plan/delete") } diff --git a/pkg/agentic/commands_platform.go b/pkg/agentic/commands_platform.go index ff616d10..25358bbe 100644 --- a/pkg/agentic/commands_platform.go +++ b/pkg/agentic/commands_platform.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerPlatformCommands() { diff --git a/pkg/agentic/commands_platform_example_test.go b/pkg/agentic/commands_platform_example_test.go index 11bab4b8..80a4f6ab 100644 --- a/pkg/agentic/commands_platform_example_test.go +++ b/pkg/agentic/commands_platform_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func ExamplePrepSubsystem_cmdFleetRegister() { s := &PrepSubsystem{ diff --git a/pkg/agentic/commands_platform_test.go b/pkg/agentic/commands_platform_test.go index 77d52bf8..f2ea0da1 100644 --- a/pkg/agentic/commands_platform_test.go +++ b/pkg/agentic/commands_platform_test.go @@ -7,34 +7,33 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" + core "dappco.re/go" ) func TestCommandsplatform_CmdFleetRegister_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "") result := subsystem.cmdFleetRegister(core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsplatform_CmdAuthProvision_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "") result := subsystem.cmdAuthProvision(core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsplatform_CmdAuthProvision_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "/v1/agent/auth/provision", r.URL.Path) - assert.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/agent/auth/provision", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - assert.True(t, bodyResult.OK) + core.AssertTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - assert.True(t, parseResult.OK) - assert.Equal(t, []any{"10.0.0.0/8", "192.168.0.0/16"}, payload["ip_restrictions"]) + core.AssertTrue(t, parseResult.OK) + core.AssertEqual(t, []any{"10.0.0.0/8", "192.168.0.0/16"}, payload["ip_restrictions"]) _, _ = w.Write([]byte(`{"data":{"id":7,"workspace_id":3,"name":"codex local","key":"ak_live_secret","prefix":"ak_live","permissions":["plans:read","plans:write"],"ip_restrictions":["10.0.0.0/8","192.168.0.0/16"],"rate_limit":60,"call_count":2,"expires_at":"2026-04-01T00:00:00Z"}}`)) })) @@ -50,11 +49,11 @@ func TestCommandsplatform_CmdAuthProvision_Good(t *testing.T) { core.Option{Key: "rate_limit", Value: 60}, core.Option{Key: "expires_at", Value: "2026-04-01T00:00:00Z"}, )) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "ip restrictions: 10.0.0.0/8,192.168.0.0/16") - assert.Contains(t, output, "prefix: ak_live") + core.AssertContains(t, output, "ip restrictions: 10.0.0.0/8,192.168.0.0/16") + core.AssertContains(t, output, "prefix: ak_live") } func TestCommandsplatform_CmdAuthRevoke_Good(t *testing.T) { @@ -66,10 +65,10 @@ func TestCommandsplatform_CmdAuthRevoke_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdAuthRevoke(core.NewOptions(core.Option{Key: "_arg", Value: "7"})) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "revoked: 7") + core.AssertContains(t, output, "revoked: 7") } func TestCommandsplatform_CmdFleetNodes_Good(t *testing.T) { @@ -81,11 +80,11 @@ func TestCommandsplatform_CmdFleetNodes_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdFleetNodes(core.NewOptions()) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "charon") - assert.Contains(t, output, "total: 1") + core.AssertContains(t, output, "charon") + core.AssertContains(t, output, "total: 1") } func TestCommandsplatform_CmdFleetEvents_Good(t *testing.T) { @@ -102,12 +101,12 @@ func TestCommandsplatform_CmdFleetEvents_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdFleetEvents(core.NewOptions(core.Option{Key: "_arg", Value: "charon"})) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "event: task.assigned") - assert.Contains(t, output, "agent: charon") - assert.Contains(t, output, "repo: core/go-io") + core.AssertContains(t, output, "event: task.assigned") + core.AssertContains(t, output, "agent: charon") + core.AssertContains(t, output, "repo: core/go-io") } func TestCommandsplatform_CmdFleetEvents_Good_FallbackToTaskNext(t *testing.T) { @@ -127,13 +126,13 @@ func TestCommandsplatform_CmdFleetEvents_Good_FallbackToTaskNext(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdFleetEvents(core.NewOptions(core.Option{Key: "_arg", Value: "charon"})) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "event: task.assigned") - assert.Contains(t, output, "agent: charon") - assert.Contains(t, output, "task id: 11") - assert.Contains(t, output, "repo: core/go-io") + core.AssertContains(t, output, "event: task.assigned") + core.AssertContains(t, output, "agent: charon") + core.AssertContains(t, output, "task id: 11") + core.AssertContains(t, output, "repo: core/go-io") } func TestCommandsplatform_CmdSyncStatus_Good(t *testing.T) { @@ -145,32 +144,32 @@ func TestCommandsplatform_CmdSyncStatus_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdSyncStatus(core.NewOptions(core.Option{Key: "_arg", Value: "charon"})) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "agent: charon") - assert.Contains(t, output, "status: online") + core.AssertContains(t, output, "agent: charon") + core.AssertContains(t, output, "status: online") } func TestCommandsplatform_CmdAuthLogin_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "") result := subsystem.cmdAuthLogin(core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsplatform_CmdAuthLogin_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "/v1/agent/auth/login", r.URL.Path) - assert.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/agent/auth/login", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - assert.True(t, bodyResult.OK) + core.AssertTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - assert.True(t, parseResult.OK) - assert.Equal(t, "654321", payload["code"]) + core.AssertTrue(t, parseResult.OK) + core.AssertEqual(t, "654321", payload["code"]) _, _ = w.Write([]byte(`{"data":{"key":{"id":42,"name":"charon","key":"ak_live_xyz","prefix":"ak_live","expires_at":"2027-01-01T00:00:00Z"}}}`)) })) @@ -186,18 +185,18 @@ func TestCommandsplatform_CmdAuthLogin_Good(t *testing.T) { output := captureStdout(t, func() { result := subsystem.cmdAuthLogin(core.NewOptions(core.Option{Key: "_arg", Value: "654321"})) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "logged in") - assert.Contains(t, output, "key prefix: ak_live") - assert.Contains(t, output, "saved to:") + core.AssertContains(t, output, "logged in") + core.AssertContains(t, output, "key prefix: ak_live") + core.AssertContains(t, output, "saved to:") // Verify the key was persisted so the next dispatch authenticates. keyPath := core.JoinPath(homeDir, ".claude", "brain.key") readResult := fs.Read(keyPath) - assert.True(t, readResult.OK) - assert.Equal(t, "ak_live_xyz", core.Trim(readResult.Value.(string))) + core.AssertTrue(t, readResult.OK) + core.AssertEqual(t, "ak_live_xyz", core.Trim(readResult.Value.(string))) } func TestCommandsplatform_CmdSubscriptionDetect_Good(t *testing.T) { @@ -209,8 +208,8 @@ func TestCommandsplatform_CmdSubscriptionDetect_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdSubscriptionDetect(core.NewOptions()) - assert.True(t, result.OK) + core.AssertTrue(t, result.OK) }) - assert.Contains(t, output, "available: claude") + core.AssertContains(t, output, "available: claude") } diff --git a/pkg/agentic/commands_resume_test.go b/pkg/agentic/commands_resume_test.go index 5fd4909f..e2b3f3f0 100644 --- a/pkg/agentic/commands_resume_test.go +++ b/pkg/agentic/commands_resume_test.go @@ -5,17 +5,15 @@ package agentic import ( "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsResume_CmdResume_Good_DryRun(t *testing.T) { s, _ := testPrepWithCore(t, nil) workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-42") - require.True(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo", ".git")).OK) - require.True(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo", ".git")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(WorkspaceStatus{ Status: "blocked", Agent: "codex", Repo: "go-io", @@ -27,14 +25,14 @@ func TestCommandsResume_CmdResume_Good_DryRun(t *testing.T) { core.Option{Key: "answer", Value: "Use the new Core API"}, core.Option{Key: "dry_run", Value: true}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ResumeOutput) - require.True(t, ok) - assert.Equal(t, "core/go-io/task-42", output.Workspace) - assert.Equal(t, "codex", output.Agent) - assert.Contains(t, output.Prompt, "Fix the failing tests") - assert.Contains(t, output.Prompt, "Use the new Core API") + core.RequireTrue(t, ok) + core.AssertEqual(t, "core/go-io/task-42", output.Workspace) + core.AssertEqual(t, "codex", output.Agent) + core.AssertContains(t, output.Prompt, "Fix the failing tests") + core.AssertContains(t, output.Prompt, "Use the new Core API") } func TestCommandsResume_CmdResume_Bad_MissingWorkspace(t *testing.T) { @@ -42,23 +40,23 @@ func TestCommandsResume_CmdResume_Bad_MissingWorkspace(t *testing.T) { result := s.cmdResume(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "workspace is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "workspace is required") } func TestCommandsResume_CmdResume_Ugly_CorruptStatus(t *testing.T) { s, _ := testPrepWithCore(t, nil) workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-42") - require.True(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo", ".git")).OK) - require.True(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), "{broken json").OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo", ".git")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), "{broken json").OK) result := s.cmdResume(core.NewOptions(core.Option{Key: "_arg", Value: "core/go-io/task-42"})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "no status.json in workspace") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "no status.json in workspace") } func TestCommandsResume_RegisterCommands_Good(t *testing.T) { @@ -66,6 +64,6 @@ func TestCommandsResume_RegisterCommands_Good(t *testing.T) { s.registerCommands(c.Context()) - assert.Contains(t, c.Commands(), "resume") - assert.Contains(t, c.Commands(), "agentic:resume") + core.AssertContains(t, c.Commands(), "resume") + core.AssertContains(t, c.Commands(), "agentic:resume") } diff --git a/pkg/agentic/commands_session.go b/pkg/agentic/commands_session.go index b8c73869..d497ce8d 100644 --- a/pkg/agentic/commands_session.go +++ b/pkg/agentic/commands_session.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerSessionCommands() { diff --git a/pkg/agentic/commands_session_test.go b/pkg/agentic/commands_session_test.go index e4da8b9c..4c2c4a3a 100644 --- a/pkg/agentic/commands_session_test.go +++ b/pkg/agentic/commands_session_test.go @@ -8,9 +8,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsSession_RegisterSessionCommands_Good(t *testing.T) { @@ -19,34 +17,34 @@ func TestCommandsSession_RegisterSessionCommands_Good(t *testing.T) { s.registerSessionCommands() - assert.Contains(t, c.Commands(), "session/get") - assert.Contains(t, c.Commands(), "agentic:session/get") - assert.Contains(t, c.Commands(), "session/list") - assert.Contains(t, c.Commands(), "agentic:session/list") - assert.Contains(t, c.Commands(), "session/handoff") - assert.Contains(t, c.Commands(), "agentic:session/handoff") - assert.Contains(t, c.Commands(), "session/start") - assert.Contains(t, c.Commands(), "agentic:session/start") - assert.Contains(t, c.Commands(), "session/continue") - assert.Contains(t, c.Commands(), "agentic:session/continue") - assert.Contains(t, c.Commands(), "session/end") - assert.Contains(t, c.Commands(), "agentic:session/end") - assert.Contains(t, c.Commands(), "session/complete") - assert.Contains(t, c.Commands(), "agentic:session/complete") - assert.Contains(t, c.Commands(), "session/log") - assert.Contains(t, c.Commands(), "agentic:session/log") - assert.Contains(t, c.Commands(), "session/artifact") - assert.Contains(t, c.Commands(), "agentic:session/artifact") - assert.Contains(t, c.Commands(), "session/resume") - assert.Contains(t, c.Commands(), "agentic:session/resume") - assert.Contains(t, c.Commands(), "session/replay") - assert.Contains(t, c.Commands(), "agentic:session/replay") + core.AssertContains(t, c.Commands(), "session/get") + core.AssertContains(t, c.Commands(), "agentic:session/get") + core.AssertContains(t, c.Commands(), "session/list") + core.AssertContains(t, c.Commands(), "agentic:session/list") + core.AssertContains(t, c.Commands(), "session/handoff") + core.AssertContains(t, c.Commands(), "agentic:session/handoff") + core.AssertContains(t, c.Commands(), "session/start") + core.AssertContains(t, c.Commands(), "agentic:session/start") + core.AssertContains(t, c.Commands(), "session/continue") + core.AssertContains(t, c.Commands(), "agentic:session/continue") + core.AssertContains(t, c.Commands(), "session/end") + core.AssertContains(t, c.Commands(), "agentic:session/end") + core.AssertContains(t, c.Commands(), "session/complete") + core.AssertContains(t, c.Commands(), "agentic:session/complete") + core.AssertContains(t, c.Commands(), "session/log") + core.AssertContains(t, c.Commands(), "agentic:session/log") + core.AssertContains(t, c.Commands(), "session/artifact") + core.AssertContains(t, c.Commands(), "agentic:session/artifact") + core.AssertContains(t, c.Commands(), "session/resume") + core.AssertContains(t, c.Commands(), "agentic:session/resume") + core.AssertContains(t, c.Commands(), "session/replay") + core.AssertContains(t, c.Commands(), "agentic:session/replay") } func TestCommandsSession_CmdSessionGet_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sessions/ses-get", r.URL.Path) - require.Equal(t, http.MethodGet, r.Method) + core.AssertEqual(t, "/v1/sessions/ses-get", r.URL.Path) + core.AssertEqual(t, http.MethodGet, r.Method) _, _ = w.Write([]byte(`{"data":{"session_id":"ses-get","plan_slug":"ax-follow-up","agent_type":"codex","status":"active","summary":"Working","created_at":"2026-03-31T12:00:00Z","updated_at":"2026-03-31T12:30:00Z","work_log":[{"type":"checkpoint","message":"started"}],"artifacts":[{"path":"pkg/agentic/session.go","action":"modified"}]}}`)) })) defer server.Close() @@ -55,22 +53,22 @@ func TestCommandsSession_CmdSessionGet_Good(t *testing.T) { output := captureStdout(t, func() { result := subsystem.cmdSessionGet(core.NewOptions(core.Option{Key: "_arg", Value: "ses-get"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "session: ses-get") - assert.Contains(t, output, "plan: ax-follow-up") - assert.Contains(t, output, "work log: 1 item(s)") - assert.Contains(t, output, "artifacts: 1 item(s)") + core.AssertContains(t, output, "session: ses-get") + core.AssertContains(t, output, "plan: ax-follow-up") + core.AssertContains(t, output, "work log: 1 item(s)") + core.AssertContains(t, output, "artifacts: 1 item(s)") } func TestCommandsSession_CmdSessionList_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sessions", r.URL.Path) - require.Equal(t, "ax-follow-up", r.URL.Query().Get("plan_slug")) - require.Equal(t, "codex", r.URL.Query().Get("agent_type")) - require.Equal(t, "active", r.URL.Query().Get("status")) - require.Equal(t, "5", r.URL.Query().Get("limit")) + core.AssertEqual(t, "/v1/sessions", r.URL.Path) + core.AssertEqual(t, "ax-follow-up", r.URL.Query().Get("plan_slug")) + core.AssertEqual(t, "codex", r.URL.Query().Get("agent_type")) + core.AssertEqual(t, "active", r.URL.Query().Get("status")) + core.AssertEqual(t, "5", r.URL.Query().Get("limit")) _, _ = w.Write([]byte(`{"data":[{"session_id":"ses-1","plan_slug":"ax-follow-up","agent_type":"codex","status":"active"},{"session_id":"ses-2","agent_type":"claude","status":"paused"}],"count":2}`)) })) defer server.Close() @@ -84,26 +82,26 @@ func TestCommandsSession_CmdSessionList_Good(t *testing.T) { core.Option{Key: "status", Value: "active"}, core.Option{Key: "limit", Value: 5}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "ses-1") - assert.Contains(t, output, "2 session(s)") + core.AssertContains(t, output, "ses-1") + core.AssertContains(t, output, "2 session(s)") } func TestCommandsSession_CmdSessionStart_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sessions", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/sessions", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "opus", payload["agent_type"]) - assert.Equal(t, "ax-follow-up", payload["plan_slug"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "opus", payload["agent_type"]) + core.AssertEqual(t, "ax-follow-up", payload["plan_slug"]) _, _ = w.Write([]byte(`{"data":{"session_id":"ses-start","plan_slug":"ax-follow-up","agent_type":"opus","status":"active"}}`)) })) @@ -114,27 +112,27 @@ func TestCommandsSession_CmdSessionStart_Good(t *testing.T) { core.Option{Key: "_arg", Value: "ax-follow-up"}, core.Option{Key: "agent_type", Value: "opus"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionOutput) - require.True(t, ok) - assert.Equal(t, "ses-start", output.Session.SessionID) - assert.Equal(t, "ax-follow-up", output.Session.PlanSlug) - assert.Equal(t, "opus", output.Session.AgentType) + core.RequireTrue(t, ok) + core.AssertEqual(t, "ses-start", output.Session.SessionID) + core.AssertEqual(t, "ax-follow-up", output.Session.PlanSlug) + core.AssertEqual(t, "opus", output.Session.AgentType) } func TestCommandsSession_CmdSessionStart_Good_CanonicalAlias(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sessions", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/sessions", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "opus", payload["agent_type"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "opus", payload["agent_type"]) _, _ = w.Write([]byte(`{"data":{"session_id":"ses-start","plan_slug":"ax-follow-up","agent_type":"opus","status":"active"}}`)) })) @@ -145,13 +143,13 @@ func TestCommandsSession_CmdSessionStart_Good_CanonicalAlias(t *testing.T) { core.Option{Key: "_arg", Value: "ax-follow-up"}, core.Option{Key: "agent_type", Value: "claude:opus"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionOutput) - require.True(t, ok) - assert.Equal(t, "ses-start", output.Session.SessionID) - assert.Equal(t, "ax-follow-up", output.Session.PlanSlug) - assert.Equal(t, "opus", output.Session.AgentType) + core.RequireTrue(t, ok) + core.AssertEqual(t, "ses-start", output.Session.SessionID) + core.AssertEqual(t, "ax-follow-up", output.Session.PlanSlug) + core.AssertEqual(t, "opus", output.Session.AgentType) } func TestCommandsSession_CmdSessionStart_Bad_MissingPlanSlug(t *testing.T) { @@ -159,9 +157,9 @@ func TestCommandsSession_CmdSessionStart_Bad_MissingPlanSlug(t *testing.T) { result := subsystem.cmdSessionStart(core.NewOptions(core.Option{Key: "agent_type", Value: "opus"})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "plan_slug is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "plan_slug is required") } func TestCommandsSession_CmdSessionStart_Bad_InvalidAgentType(t *testing.T) { @@ -172,9 +170,9 @@ func TestCommandsSession_CmdSessionStart_Bad_InvalidAgentType(t *testing.T) { core.Option{Key: "agent_type", Value: "codex"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "claude:opus") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "claude:opus") } func TestCommandsSession_CmdSessionStart_Ugly_InvalidResponse(t *testing.T) { @@ -188,21 +186,21 @@ func TestCommandsSession_CmdSessionStart_Ugly_InvalidResponse(t *testing.T) { core.Option{Key: "_arg", Value: "ax-follow-up"}, core.Option{Key: "agent_type", Value: "codex"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsSession_CmdSessionContinue_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sessions/ses-continue/continue", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/sessions/ses-continue/continue", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "codex", payload["agent_type"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "codex", payload["agent_type"]) _, _ = w.Write([]byte(`{"data":{"session_id":"ses-continue","agent_type":"codex","status":"active","work_log":[{"type":"checkpoint","message":"continue"}]}}`)) })) @@ -214,13 +212,13 @@ func TestCommandsSession_CmdSessionContinue_Good(t *testing.T) { core.Option{Key: "agent_type", Value: "codex"}, core.Option{Key: "work_log", Value: []map[string]any{{"type": "checkpoint", "message": "continue"}}}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionOutput) - require.True(t, ok) - assert.Equal(t, "ses-continue", output.Session.SessionID) - assert.Equal(t, "codex", output.Session.AgentType) - require.Len(t, output.Session.WorkLog, 1) + core.RequireTrue(t, ok) + core.AssertEqual(t, "ses-continue", output.Session.SessionID) + core.AssertEqual(t, "codex", output.Session.AgentType) + core.AssertLen(t, output.Session.WorkLog, 1) } func TestCommandsSession_CmdSessionContinue_Bad_MissingSessionID(t *testing.T) { @@ -228,9 +226,9 @@ func TestCommandsSession_CmdSessionContinue_Bad_MissingSessionID(t *testing.T) { result := subsystem.cmdSessionContinue(core.NewOptions(core.Option{Key: "agent_type", Value: "codex"})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "session_id is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "session_id is required") } func TestCommandsSession_CmdSessionContinue_Ugly_InvalidResponse(t *testing.T) { @@ -244,7 +242,7 @@ func TestCommandsSession_CmdSessionContinue_Ugly_InvalidResponse(t *testing.T) { core.Option{Key: "_arg", Value: "ses-continue"}, core.Option{Key: "agent_type", Value: "codex"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsSession_CmdSessionHandoff_Good(t *testing.T) { @@ -252,7 +250,7 @@ func TestCommandsSession_CmdSessionHandoff_Good(t *testing.T) { setTestWorkspace(t, dir) s := newTestPrep(t) - require.NoError(t, writeSessionCache(&Session{ + core.RequireNoError(t, writeSessionCache(&Session{ SessionID: "ses-handoff", AgentType: "codex", Status: "active", @@ -269,21 +267,21 @@ func TestCommandsSession_CmdSessionHandoff_Good(t *testing.T) { core.Option{Key: "blockers", Value: []string{"Need final approval"}}, core.Option{Key: "context_for_next", Value: map[string]any{"repo": "go-io"}}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionHandoffOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "ses-handoff", output.HandoffContext["session_id"]) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "ses-handoff", output.HandoffContext["session_id"]) handoffNotes, ok := output.HandoffContext["handoff_notes"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "Ready for review", handoffNotes["summary"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Ready for review", handoffNotes["summary"]) cached, err := readSessionCache("ses-handoff") - require.NoError(t, err) - require.NotNil(t, cached) - assert.Equal(t, "handed_off", cached.Status) - assert.NotEmpty(t, cached.Handoff) + core.RequireNoError(t, err) + core.AssertNotNil(t, cached) + core.AssertEqual(t, "handed_off", cached.Status) + core.AssertNotEmpty(t, cached.Handoff) } func TestCommandsSession_CmdSessionHandoff_Bad_MissingSummary(t *testing.T) { @@ -291,9 +289,9 @@ func TestCommandsSession_CmdSessionHandoff_Bad_MissingSummary(t *testing.T) { result := s.cmdSessionHandoff(core.NewOptions(core.Option{Key: "session_id", Value: "ses-handoff"})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "summary is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "summary is required") } func TestCommandsSession_CmdSessionHandoff_Ugly_CorruptedCacheFallsBackToRemoteError(t *testing.T) { @@ -301,17 +299,17 @@ func TestCommandsSession_CmdSessionHandoff_Ugly_CorruptedCacheFallsBackToRemoteE setTestWorkspace(t, dir) s := newTestPrep(t) - require.True(t, fs.EnsureDir(sessionCacheRoot()).OK) - require.True(t, fs.WriteAtomic(sessionCachePath("ses-bad"), "{not-json").OK) + core.RequireTrue(t, fs.EnsureDir(sessionCacheRoot()).OK) + core.RequireTrue(t, fs.WriteAtomic(sessionCachePath("ses-bad"), "{not-json").OK) result := s.cmdSessionHandoff(core.NewOptions( core.Option{Key: "session_id", Value: "ses-bad"}, core.Option{Key: "summary", Value: "Ready for review"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "no platform API key configured") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "no platform API key configured") } func TestCommandsSession_CmdSessionEnd_Good(t *testing.T) { @@ -320,40 +318,40 @@ func TestCommandsSession_CmdSessionEnd_Good(t *testing.T) { callCount++ switch r.URL.Path { case "/v1/sessions/ses-end/end": - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "completed", payload["status"]) - require.Equal(t, "Ready for review", payload["summary"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "completed", payload["status"]) + core.AssertEqual(t, "Ready for review", payload["summary"]) handoffNotes, ok := payload["handoff_notes"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "Ready for review", handoffNotes["summary"]) - assert.Equal(t, []any{"Run the verifier"}, handoffNotes["next_steps"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Ready for review", handoffNotes["summary"]) + core.AssertEqual(t, []any{"Run the verifier"}, handoffNotes["next_steps"]) _, _ = w.Write([]byte(`{"data":{"session_id":"ses-end","agent_type":"codex","status":"completed","summary":"Ready for review","handoff":{"summary":"Ready for review","next_steps":["Run the verifier"]},"ended_at":"2026-03-31T12:00:00Z"}}`)) case "/v1/brain/remember": - require.Equal(t, http.MethodPost, r.Method) - require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "Bearer secret-token", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "observation", payload["type"]) - assert.Equal(t, "codex", payload["agent_id"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "observation", payload["type"]) + core.AssertEqual(t, "codex", payload["agent_id"]) content, _ := payload["content"].(string) - assert.Contains(t, content, "Session handoff: ses-end") - assert.Contains(t, content, "Ready for review") - assert.Contains(t, content, "Run the verifier") + core.AssertContains(t, content, "Session handoff: ses-end") + core.AssertContains(t, content, "Ready for review") + core.AssertContains(t, content, "Run the verifier") _, _ = w.Write([]byte(`{"data":{"id":"mem_end"}}`)) default: @@ -368,15 +366,15 @@ func TestCommandsSession_CmdSessionEnd_Good(t *testing.T) { core.Option{Key: "summary", Value: "Ready for review"}, core.Option{Key: "handoff_notes", Value: `{"summary":"Ready for review","next_steps":["Run the verifier"]}`}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionOutput) - require.True(t, ok) - assert.Equal(t, "completed", output.Session.Status) - assert.Equal(t, "Ready for review", output.Session.Summary) - require.NotNil(t, output.Session.Handoff) - assert.Equal(t, "Ready for review", output.Session.Handoff["summary"]) - assert.Equal(t, 2, callCount) + core.RequireTrue(t, ok) + core.AssertEqual(t, "completed", output.Session.Status) + core.AssertEqual(t, "Ready for review", output.Session.Summary) + core.AssertNotNil(t, output.Session.Handoff) + core.AssertEqual(t, "Ready for review", output.Session.Handoff["summary"]) + core.AssertEqual(t, 2, callCount) } func TestCommandsSession_CmdSessionEnd_Bad_MissingSummary(t *testing.T) { @@ -384,9 +382,9 @@ func TestCommandsSession_CmdSessionEnd_Bad_MissingSummary(t *testing.T) { result := subsystem.cmdSessionEnd(core.NewOptions( core.Option{Key: "session_id", Value: "ses-end"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "summary is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "summary is required") } func TestCommandsSession_CmdSessionEnd_Ugly_InvalidResponse(t *testing.T) { @@ -400,7 +398,7 @@ func TestCommandsSession_CmdSessionEnd_Ugly_InvalidResponse(t *testing.T) { core.Option{Key: "session_id", Value: "ses-end"}, core.Option{Key: "summary", Value: "Ready for review"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsSession_CmdSessionLog_Good(t *testing.T) { @@ -408,7 +406,7 @@ func TestCommandsSession_CmdSessionLog_Good(t *testing.T) { setTestWorkspace(t, dir) s := newTestPrep(t) - require.NoError(t, writeSessionCache(&Session{ + core.RequireNoError(t, writeSessionCache(&Session{ SessionID: "ses-log", AgentType: "codex", Status: "active", @@ -423,19 +421,19 @@ func TestCommandsSession_CmdSessionLog_Good(t *testing.T) { core.Option{Key: "type", Value: "checkpoint"}, core.Option{Key: "data", Value: map[string]any{"repo": "go-io"}}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionLogOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "Checked build", output.Logged) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "Checked build", output.Logged) cached, err := readSessionCache("ses-log") - require.NoError(t, err) - require.NotNil(t, cached) - require.Len(t, cached.WorkLog, 2) - assert.Equal(t, "checkpoint", cached.WorkLog[1]["type"]) - assert.Equal(t, "Checked build", cached.WorkLog[1]["message"]) + core.RequireNoError(t, err) + core.AssertNotNil(t, cached) + core.AssertLen(t, cached.WorkLog, 2) + core.AssertEqual(t, "checkpoint", cached.WorkLog[1]["type"]) + core.AssertEqual(t, "Checked build", cached.WorkLog[1]["message"]) } func TestCommandsSession_CmdSessionLog_Bad_MissingMessage(t *testing.T) { @@ -443,9 +441,9 @@ func TestCommandsSession_CmdSessionLog_Bad_MissingMessage(t *testing.T) { result := s.cmdSessionLog(core.NewOptions(core.Option{Key: "session_id", Value: "ses-log"})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "message is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "message is required") } func TestCommandsSession_CmdSessionLog_Ugly_CorruptedCacheFallsBackToRemoteError(t *testing.T) { @@ -453,17 +451,17 @@ func TestCommandsSession_CmdSessionLog_Ugly_CorruptedCacheFallsBackToRemoteError setTestWorkspace(t, dir) s := newTestPrep(t) - require.True(t, fs.EnsureDir(sessionCacheRoot()).OK) - require.True(t, fs.WriteAtomic(sessionCachePath("ses-bad"), "{not-json").OK) + core.RequireTrue(t, fs.EnsureDir(sessionCacheRoot()).OK) + core.RequireTrue(t, fs.WriteAtomic(sessionCachePath("ses-bad"), "{not-json").OK) result := s.cmdSessionLog(core.NewOptions( core.Option{Key: "session_id", Value: "ses-bad"}, core.Option{Key: "message", Value: "Checked build"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "no platform API key configured") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "no platform API key configured") } func TestCommandsSession_CmdSessionArtifact_Good(t *testing.T) { @@ -471,7 +469,7 @@ func TestCommandsSession_CmdSessionArtifact_Good(t *testing.T) { setTestWorkspace(t, dir) s := newTestPrep(t) - require.NoError(t, writeSessionCache(&Session{ + core.RequireNoError(t, writeSessionCache(&Session{ SessionID: "ses-artifact", AgentType: "codex", Status: "active", @@ -484,23 +482,23 @@ func TestCommandsSession_CmdSessionArtifact_Good(t *testing.T) { core.Option{Key: "description", Value: "Tracked session metadata"}, core.Option{Key: "metadata", Value: map[string]any{"repo": "go-agent"}}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionArtifactOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "pkg/agentic/session.go", output.Artifact) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "pkg/agentic/session.go", output.Artifact) cached, err := readSessionCache("ses-artifact") - require.NoError(t, err) - require.NotNil(t, cached) - require.Len(t, cached.Artifacts, 1) - assert.Equal(t, "modified", cached.Artifacts[0]["action"]) - assert.Equal(t, "pkg/agentic/session.go", cached.Artifacts[0]["path"]) + core.RequireNoError(t, err) + core.AssertNotNil(t, cached) + core.AssertLen(t, cached.Artifacts, 1) + core.AssertEqual(t, "modified", cached.Artifacts[0]["action"]) + core.AssertEqual(t, "pkg/agentic/session.go", cached.Artifacts[0]["path"]) metadata, ok := cached.Artifacts[0]["metadata"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "Tracked session metadata", metadata["description"]) - assert.Equal(t, "go-agent", metadata["repo"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Tracked session metadata", metadata["description"]) + core.AssertEqual(t, "go-agent", metadata["repo"]) } func TestCommandsSession_CmdSessionArtifact_Bad_MissingPath(t *testing.T) { @@ -511,9 +509,9 @@ func TestCommandsSession_CmdSessionArtifact_Bad_MissingPath(t *testing.T) { core.Option{Key: "action", Value: "modified"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "path is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "path is required") } func TestCommandsSession_CmdSessionResume_Good(t *testing.T) { @@ -521,7 +519,7 @@ func TestCommandsSession_CmdSessionResume_Good(t *testing.T) { setTestWorkspace(t, dir) s := newTestPrep(t) - require.NoError(t, writeSessionCache(&Session{ + core.RequireNoError(t, writeSessionCache(&Session{ SessionID: "ses-abc123", AgentType: "codex", Status: "paused", @@ -539,19 +537,19 @@ func TestCommandsSession_CmdSessionResume_Good(t *testing.T) { })) result := s.cmdSessionResume(core.NewOptions(core.Option{Key: "session_id", Value: "ses-abc123"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionResumeOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "ses-abc123", output.Session.SessionID) - assert.Equal(t, "active", output.Session.Status) - assert.Equal(t, "ses-abc123", output.HandoffContext["session_id"]) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "ses-abc123", output.Session.SessionID) + core.AssertEqual(t, "active", output.Session.Status) + core.AssertEqual(t, "ses-abc123", output.HandoffContext["session_id"]) handoffNotes, ok := output.HandoffContext["handoff_notes"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "Ready for review", handoffNotes["summary"]) - assert.Len(t, output.RecentActions, 2) - assert.Len(t, output.Artifacts, 1) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Ready for review", handoffNotes["summary"]) + core.AssertLen(t, output.RecentActions, 2) + core.AssertLen(t, output.Artifacts, 1) } func TestCommandsSession_CmdSessionResume_Bad_MissingSessionID(t *testing.T) { @@ -559,9 +557,9 @@ func TestCommandsSession_CmdSessionResume_Bad_MissingSessionID(t *testing.T) { result := s.cmdSessionResume(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "session_id is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "session_id is required") } func TestCommandsSession_CmdSessionResume_Ugly_CorruptedCacheFallsBackToRemoteError(t *testing.T) { @@ -569,14 +567,14 @@ func TestCommandsSession_CmdSessionResume_Ugly_CorruptedCacheFallsBackToRemoteEr setTestWorkspace(t, dir) s := newTestPrep(t) - require.True(t, fs.EnsureDir(sessionCacheRoot()).OK) - require.True(t, fs.WriteAtomic(sessionCachePath("ses-bad"), "{not-json").OK) + core.RequireTrue(t, fs.EnsureDir(sessionCacheRoot()).OK) + core.RequireTrue(t, fs.WriteAtomic(sessionCachePath("ses-bad"), "{not-json").OK) result := s.cmdSessionResume(core.NewOptions(core.Option{Key: "session_id", Value: "ses-bad"})) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "no platform API key configured") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "no platform API key configured") } func TestCommandsSession_CmdSessionReplay_Good(t *testing.T) { @@ -584,7 +582,7 @@ func TestCommandsSession_CmdSessionReplay_Good(t *testing.T) { setTestWorkspace(t, dir) s := newTestPrep(t) - require.NoError(t, writeSessionCache(&Session{ + core.RequireNoError(t, writeSessionCache(&Session{ SessionID: "ses-replay", AgentType: "codex", Status: "active", @@ -599,15 +597,15 @@ func TestCommandsSession_CmdSessionReplay_Good(t *testing.T) { })) result := s.cmdSessionReplay(core.NewOptions(core.Option{Key: "session_id", Value: "ses-replay"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(SessionReplayOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "ses-replay", output.ReplayContext["session_id"]) - assert.Contains(t, output.ReplayContext, "checkpoints") - assert.Contains(t, output.ReplayContext, "decisions") - assert.Contains(t, output.ReplayContext, "errors") + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "ses-replay", output.ReplayContext["session_id"]) + core.AssertContains(t, output.ReplayContext, "checkpoints") + core.AssertContains(t, output.ReplayContext, "decisions") + core.AssertContains(t, output.ReplayContext, "errors") } func TestCommandsSession_CmdSessionReplay_Bad_MissingSessionID(t *testing.T) { @@ -615,7 +613,7 @@ func TestCommandsSession_CmdSessionReplay_Bad_MissingSessionID(t *testing.T) { result := s.cmdSessionReplay(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "session_id is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "session_id is required") } diff --git a/pkg/agentic/commands_setup.go b/pkg/agentic/commands_setup.go index ae4d5f4e..804ce153 100644 --- a/pkg/agentic/commands_setup.go +++ b/pkg/agentic/commands_setup.go @@ -5,8 +5,8 @@ package agentic import ( "context" + core "dappco.re/go" "dappco.re/go/agent/pkg/setup" - core "dappco.re/go/core" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/commands_setup_test.go b/pkg/agentic/commands_setup_test.go index 0fe60d27..75dfef90 100644 --- a/pkg/agentic/commands_setup_test.go +++ b/pkg/agentic/commands_setup_test.go @@ -7,15 +7,13 @@ import ( "testing" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/setup" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCommandsSetup_CmdSetup_Good_WritesCoreConfigs(t *testing.T) { dir := t.TempDir() - require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) + core.RequireTrue(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) c := core.New(core.WithService(setup.Register)) s := &PrepSubsystem{ @@ -25,11 +23,11 @@ func TestCommandsSetup_CmdSetup_Good_WritesCoreConfigs(t *testing.T) { } result := s.cmdSetup(core.NewOptions(core.Option{Key: "path", Value: dir})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) build := fs.Read(core.JoinPath(dir, ".core", "build.yaml")) - require.True(t, build.OK) - assert.Contains(t, build.Value.(string), "type: go") + core.RequireTrue(t, build.OK) + core.AssertContains(t, build.Value.(string), "type: go") } func TestCommandsSetup_CmdSetup_Bad_MissingService(t *testing.T) { @@ -40,14 +38,14 @@ func TestCommandsSetup_CmdSetup_Bad_MissingService(t *testing.T) { } result := s.cmdSetup(core.NewOptions()) - require.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "setup service is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "setup service is required") } func TestCommandsSetup_CmdSetup_Ugly_DryRunDoesNotWrite(t *testing.T) { dir := t.TempDir() - require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) + core.RequireTrue(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) c := core.New(core.WithService(setup.Register)) s := &PrepSubsystem{ @@ -61,14 +59,14 @@ func TestCommandsSetup_CmdSetup_Ugly_DryRunDoesNotWrite(t *testing.T) { core.Option{Key: "dry-run", Value: true}, core.Option{Key: "template", Value: "agent"}, )) - require.True(t, result.OK) - assert.False(t, fs.Exists(core.JoinPath(dir, ".core"))) - assert.False(t, fs.Exists(core.JoinPath(dir, "PROMPT.md"))) + core.RequireTrue(t, result.OK) + core.AssertFalse(t, fs.Exists(core.JoinPath(dir, ".core"))) + core.AssertFalse(t, fs.Exists(core.JoinPath(dir, "PROMPT.md"))) } func TestCommandsSetup_HandleSetup_Good_ActionAlias(t *testing.T) { dir := t.TempDir() - require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) + core.RequireTrue(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK) c := core.New(core.WithService(setup.Register)) s := &PrepSubsystem{ @@ -78,10 +76,10 @@ func TestCommandsSetup_HandleSetup_Good_ActionAlias(t *testing.T) { } result := s.handleSetup(context.Background(), core.NewOptions(core.Option{Key: "path", Value: dir})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) createdPath, ok := result.Value.(string) - require.True(t, ok) - assert.Equal(t, dir, createdPath) - assert.True(t, fs.Exists(core.JoinPath(dir, ".core", "build.yaml"))) + core.RequireTrue(t, ok) + core.AssertEqual(t, dir, createdPath) + core.AssertTrue(t, fs.Exists(core.JoinPath(dir, ".core", "build.yaml"))) } diff --git a/pkg/agentic/commands_sprint.go b/pkg/agentic/commands_sprint.go index 2296b48b..ade3071c 100644 --- a/pkg/agentic/commands_sprint.go +++ b/pkg/agentic/commands_sprint.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerSprintCommands() { diff --git a/pkg/agentic/commands_sprint_test.go b/pkg/agentic/commands_sprint_test.go index b403bc9f..c57c43b1 100644 --- a/pkg/agentic/commands_sprint_test.go +++ b/pkg/agentic/commands_sprint_test.go @@ -7,9 +7,7 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsSprint_RegisterCommands_Good(t *testing.T) { @@ -18,33 +16,33 @@ func TestCommandsSprint_RegisterCommands_Good(t *testing.T) { s.registerSprintCommands() - assert.Contains(t, c.Commands(), "sprint") - assert.Contains(t, c.Commands(), "agentic:sprint") - assert.Contains(t, c.Commands(), "sprint/create") - assert.Contains(t, c.Commands(), "agentic:sprint/create") - assert.Contains(t, c.Commands(), "sprint/get") - assert.Contains(t, c.Commands(), "agentic:sprint/get") - assert.Contains(t, c.Commands(), "sprint/list") - assert.Contains(t, c.Commands(), "agentic:sprint/list") - assert.Contains(t, c.Commands(), "sprint/update") - assert.Contains(t, c.Commands(), "agentic:sprint/update") - assert.Contains(t, c.Commands(), "sprint/archive") - assert.Contains(t, c.Commands(), "agentic:sprint/archive") + core.AssertContains(t, c.Commands(), "sprint") + core.AssertContains(t, c.Commands(), "agentic:sprint") + core.AssertContains(t, c.Commands(), "sprint/create") + core.AssertContains(t, c.Commands(), "agentic:sprint/create") + core.AssertContains(t, c.Commands(), "sprint/get") + core.AssertContains(t, c.Commands(), "agentic:sprint/get") + core.AssertContains(t, c.Commands(), "sprint/list") + core.AssertContains(t, c.Commands(), "agentic:sprint/list") + core.AssertContains(t, c.Commands(), "sprint/update") + core.AssertContains(t, c.Commands(), "agentic:sprint/update") + core.AssertContains(t, c.Commands(), "sprint/archive") + core.AssertContains(t, c.Commands(), "agentic:sprint/archive") } func TestCommandsSprint_CmdSprintCreate_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sprints", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/sprints", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) - require.Equal(t, "AX Follow-up", payload["title"]) - require.Equal(t, "Finish RFC parity", payload["goal"]) - require.Equal(t, "active", payload["status"]) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.AssertEqual(t, "AX Follow-up", payload["title"]) + core.AssertEqual(t, "Finish RFC parity", payload["goal"]) + core.AssertEqual(t, "active", payload["status"]) _, _ = w.Write([]byte(`{"data":{"sprint":{"id":7,"slug":"ax-follow-up","title":"AX Follow-up","goal":"Finish RFC parity","status":"active"}}}`)) })) @@ -57,20 +55,20 @@ func TestCommandsSprint_CmdSprintCreate_Good(t *testing.T) { core.Option{Key: "goal", Value: "Finish RFC parity"}, core.Option{Key: "status", Value: "active"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "slug: ax-follow-up") - assert.Contains(t, output, "title: AX Follow-up") - assert.Contains(t, output, "status: active") - assert.Contains(t, output, "goal: Finish RFC parity") + core.AssertContains(t, output, "slug: ax-follow-up") + core.AssertContains(t, output, "title: AX Follow-up") + core.AssertContains(t, output, "status: active") + core.AssertContains(t, output, "goal: Finish RFC parity") } func TestCommandsSprint_CmdSprintList_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/sprints", r.URL.Path) - require.Equal(t, "active", r.URL.Query().Get("status")) - require.Equal(t, "5", r.URL.Query().Get("limit")) + core.AssertEqual(t, "/v1/sprints", r.URL.Path) + core.AssertEqual(t, "active", r.URL.Query().Get("status")) + core.AssertEqual(t, "5", r.URL.Query().Get("limit")) _, _ = w.Write([]byte(`{"data":[{"id":1,"slug":"ax-follow-up","title":"AX Follow-up","status":"active"},{"id":2,"slug":"rfc-parity","title":"RFC Parity","status":"active"}],"count":2}`)) })) @@ -82,12 +80,12 @@ func TestCommandsSprint_CmdSprintList_Good(t *testing.T) { core.Option{Key: "status", Value: "active"}, core.Option{Key: "limit", Value: 5}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "ax-follow-up") - assert.Contains(t, output, "rfc-parity") - assert.Contains(t, output, "2 sprint(s)") + core.AssertContains(t, output, "ax-follow-up") + core.AssertContains(t, output, "rfc-parity") + core.AssertContains(t, output, "2 sprint(s)") } func TestCommandsSprint_CmdSprintArchive_Bad_MissingIdentifier(t *testing.T) { @@ -95,9 +93,9 @@ func TestCommandsSprint_CmdSprintArchive_Bad_MissingIdentifier(t *testing.T) { result := subsystem.cmdSprintArchive(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "id or slug is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "id or slug is required") } func TestCommandsSprint_CmdSprintGet_Ugly_InvalidResponse(t *testing.T) { @@ -108,5 +106,5 @@ func TestCommandsSprint_CmdSprintGet_Ugly_InvalidResponse(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") result := subsystem.cmdSprintGet(core.NewOptions(core.Option{Key: "_arg", Value: "ax-follow-up"})) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } diff --git a/pkg/agentic/commands_state.go b/pkg/agentic/commands_state.go index 94de35f2..9fc07747 100644 --- a/pkg/agentic/commands_state.go +++ b/pkg/agentic/commands_state.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerStateCommands() { diff --git a/pkg/agentic/commands_state_test.go b/pkg/agentic/commands_state_test.go index c3816461..d0f2fb8e 100644 --- a/pkg/agentic/commands_state_test.go +++ b/pkg/agentic/commands_state_test.go @@ -5,9 +5,7 @@ package agentic import ( "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommandsState_RegisterStateCommands_Good(t *testing.T) { @@ -15,12 +13,12 @@ func TestCommandsState_RegisterStateCommands_Good(t *testing.T) { s.registerStateCommands() - assert.Contains(t, c.Commands(), "state") - assert.Contains(t, c.Commands(), "agentic:state") - assert.Contains(t, c.Commands(), "state/set") - assert.Contains(t, c.Commands(), "state/get") - assert.Contains(t, c.Commands(), "state/list") - assert.Contains(t, c.Commands(), "state/delete") + core.AssertContains(t, c.Commands(), "state") + core.AssertContains(t, c.Commands(), "agentic:state") + core.AssertContains(t, c.Commands(), "state/set") + core.AssertContains(t, c.Commands(), "state/get") + core.AssertContains(t, c.Commands(), "state/list") + core.AssertContains(t, c.Commands(), "state/delete") } func TestCommandsState_CmdStateSet_Good(t *testing.T) { @@ -34,14 +32,14 @@ func TestCommandsState_CmdStateSet_Good(t *testing.T) { core.Option{Key: "description", Value: "Shared across sessions"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(StateOutput) - require.True(t, ok) - assert.Equal(t, "pattern", output.State.Key) - assert.Equal(t, "general", output.State.Type) - assert.Equal(t, "observer", output.State.Value) - assert.Equal(t, "Shared across sessions", output.State.Description) + core.RequireTrue(t, ok) + core.AssertEqual(t, "pattern", output.State.Key) + core.AssertEqual(t, "general", output.State.Type) + core.AssertEqual(t, "observer", output.State.Value) + core.AssertEqual(t, "Shared across sessions", output.State.Description) } func TestCommandsState_CmdStateSet_Bad_MissingValue(t *testing.T) { @@ -52,7 +50,7 @@ func TestCommandsState_CmdStateSet_Bad_MissingValue(t *testing.T) { core.Option{Key: "key", Value: "pattern"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsState_CmdStateGet_Good(t *testing.T) { @@ -64,19 +62,19 @@ func TestCommandsState_CmdStateGet_Good(t *testing.T) { Value: "observer", Type: "general", }) - require.NoError(t, err) - require.Equal(t, "pattern", output.State.Key) + core.RequireNoError(t, err) + core.AssertEqual(t, "pattern", output.State.Key) result := s.cmdStateGet(core.NewOptions( core.Option{Key: "_arg", Value: "ax-follow-up"}, core.Option{Key: "key", Value: "pattern"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) stateOutput, ok := result.Value.(StateOutput) - require.True(t, ok) - assert.Equal(t, "pattern", stateOutput.State.Key) - assert.Equal(t, "observer", stateOutput.State.Value) + core.RequireTrue(t, ok) + core.AssertEqual(t, "pattern", stateOutput.State.Key) + core.AssertEqual(t, "observer", stateOutput.State.Value) } func TestCommandsState_CmdStateGet_Bad_MissingKey(t *testing.T) { @@ -84,7 +82,7 @@ func TestCommandsState_CmdStateGet_Bad_MissingKey(t *testing.T) { result := s.cmdStateGet(core.NewOptions(core.Option{Key: "_arg", Value: "ax-follow-up"})) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestCommandsState_CmdStateList_Good(t *testing.T) { @@ -96,15 +94,15 @@ func TestCommandsState_CmdStateList_Good(t *testing.T) { Value: "observer", Type: "general", }) - require.NoError(t, err) + core.RequireNoError(t, err) result := s.cmdStateList(core.NewOptions(core.Option{Key: "_arg", Value: "ax-follow-up"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) listOutput, ok := result.Value.(StateListOutput) - require.True(t, ok) - assert.Equal(t, 1, listOutput.Total) - assert.Len(t, listOutput.States, 1) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, listOutput.Total) + core.AssertLen(t, listOutput.States, 1) } func TestCommandsState_CmdStateList_Ugly_EmptyPlan(t *testing.T) { @@ -112,11 +110,11 @@ func TestCommandsState_CmdStateList_Ugly_EmptyPlan(t *testing.T) { result := s.cmdStateList(core.NewOptions(core.Option{Key: "_arg", Value: "ax-follow-up"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) listOutput, ok := result.Value.(StateListOutput) - require.True(t, ok) - assert.Zero(t, listOutput.Total) - assert.Empty(t, listOutput.States) + core.RequireTrue(t, ok) + assertZero(t, listOutput.Total) + core.AssertEmpty(t, listOutput.States) } func TestCommandsState_CmdStateDelete_Good(t *testing.T) { @@ -128,18 +126,18 @@ func TestCommandsState_CmdStateDelete_Good(t *testing.T) { Value: "observer", Type: "general", }) - require.NoError(t, err) + core.RequireNoError(t, err) result := s.cmdStateDelete(core.NewOptions( core.Option{Key: "_arg", Value: "ax-follow-up"}, core.Option{Key: "key", Value: "pattern"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) deleteOutput, ok := result.Value.(StateDeleteOutput) - require.True(t, ok) - assert.Equal(t, "pattern", deleteOutput.Deleted.Key) - assert.False(t, fs.Exists(statePath("ax-follow-up"))) + core.RequireTrue(t, ok) + core.AssertEqual(t, "pattern", deleteOutput.Deleted.Key) + core.AssertFalse(t, fs.Exists(statePath("ax-follow-up"))) } func TestCommandsState_CmdStateDelete_Bad_MissingKey(t *testing.T) { @@ -147,5 +145,5 @@ func TestCommandsState_CmdStateDelete_Bad_MissingKey(t *testing.T) { result := s.cmdStateDelete(core.NewOptions(core.Option{Key: "_arg", Value: "ax-follow-up"})) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } diff --git a/pkg/agentic/commands_task.go b/pkg/agentic/commands_task.go index 35706b79..c33b1a85 100644 --- a/pkg/agentic/commands_task.go +++ b/pkg/agentic/commands_task.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerTaskCommands() { diff --git a/pkg/agentic/commands_task_test.go b/pkg/agentic/commands_task_test.go index 9112c817..f9810180 100644 --- a/pkg/agentic/commands_task_test.go +++ b/pkg/agentic/commands_task_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommands_TaskCommand_Good_Update(t *testing.T) { @@ -23,10 +21,10 @@ func TestCommands_TaskCommand_Good_Update(t *testing.T) { {Name: "Setup", Tasks: []PlanTask{{ID: "1", Title: "Review RFC"}}}, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdTaskUpdate(core.NewOptions( core.Option{Key: "plan_slug", Value: plan.Slug}, @@ -39,16 +37,16 @@ func TestCommands_TaskCommand_Good_Update(t *testing.T) { core.Option{Key: "file", Value: "pkg/agentic/task.go"}, core.Option{Key: "line", Value: 128}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(TaskOutput) - require.True(t, ok) - assert.Equal(t, "completed", output.Task.Status) - assert.Equal(t, "Done", output.Task.Notes) - assert.Equal(t, "high", output.Task.Priority) - assert.Equal(t, "security", output.Task.Category) - assert.Equal(t, "pkg/agentic/task.go", output.Task.File) - assert.Equal(t, 128, output.Task.Line) + core.RequireTrue(t, ok) + core.AssertEqual(t, "completed", output.Task.Status) + core.AssertEqual(t, "Done", output.Task.Notes) + core.AssertEqual(t, "high", output.Task.Priority) + core.AssertEqual(t, "security", output.Task.Category) + core.AssertEqual(t, "pkg/agentic/task.go", output.Task.File) + core.AssertEqual(t, 128, output.Task.Line) } func TestCommands_TaskCommand_Good_SpecAliasRegistered(t *testing.T) { @@ -57,10 +55,10 @@ func TestCommands_TaskCommand_Good_SpecAliasRegistered(t *testing.T) { s.registerTaskCommands() - assert.Contains(t, c.Commands(), "agentic:task") - assert.Contains(t, c.Commands(), "agentic:task/create") - assert.Contains(t, c.Commands(), "agentic:task/update") - assert.Contains(t, c.Commands(), "agentic:task/toggle") + core.AssertContains(t, c.Commands(), "agentic:task") + core.AssertContains(t, c.Commands(), "agentic:task/create") + core.AssertContains(t, c.Commands(), "agentic:task/update") + core.AssertContains(t, c.Commands(), "agentic:task/toggle") } func TestCommands_TaskCommand_Good_Create(t *testing.T) { @@ -75,10 +73,10 @@ func TestCommands_TaskCommand_Good_Create(t *testing.T) { {Name: "Setup"}, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdTaskCreate(core.NewOptions( core.Option{Key: "plan_slug", Value: plan.Slug}, @@ -92,17 +90,17 @@ func TestCommands_TaskCommand_Good_Create(t *testing.T) { core.Option{Key: "file", Value: "pkg/agentic/task.go"}, core.Option{Key: "line", Value: 153}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(TaskCreateOutput) - require.True(t, ok) - assert.Equal(t, "Patch code", output.Task.Title) - assert.Equal(t, "pending", output.Task.Status) - assert.Equal(t, "Do this first", output.Task.Notes) - assert.Equal(t, "high", output.Task.Priority) - assert.Equal(t, "implementation", output.Task.Category) - assert.Equal(t, "pkg/agentic/task.go", output.Task.File) - assert.Equal(t, 153, output.Task.Line) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Patch code", output.Task.Title) + core.AssertEqual(t, "pending", output.Task.Status) + core.AssertEqual(t, "Do this first", output.Task.Notes) + core.AssertEqual(t, "high", output.Task.Priority) + core.AssertEqual(t, "implementation", output.Task.Category) + core.AssertEqual(t, "pkg/agentic/task.go", output.Task.File) + core.AssertEqual(t, 153, output.Task.Line) } func TestCommands_TaskCommand_Good_CreateFileRefAliases(t *testing.T) { @@ -117,10 +115,10 @@ func TestCommands_TaskCommand_Good_CreateFileRefAliases(t *testing.T) { {Name: "Setup"}, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdTaskCreate(core.NewOptions( core.Option{Key: "plan_slug", Value: plan.Slug}, @@ -129,14 +127,14 @@ func TestCommands_TaskCommand_Good_CreateFileRefAliases(t *testing.T) { core.Option{Key: "file_ref", Value: "pkg/agentic/task.go"}, core.Option{Key: "line_ref", Value: 153}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(TaskCreateOutput) - require.True(t, ok) - assert.Equal(t, "pkg/agentic/task.go", output.Task.FileRef) - assert.Equal(t, 153, output.Task.LineRef) - assert.Equal(t, "pkg/agentic/task.go", output.Task.File) - assert.Equal(t, 153, output.Task.Line) + core.RequireTrue(t, ok) + core.AssertEqual(t, "pkg/agentic/task.go", output.Task.FileRef) + core.AssertEqual(t, 153, output.Task.LineRef) + core.AssertEqual(t, "pkg/agentic/task.go", output.Task.File) + core.AssertEqual(t, 153, output.Task.Line) } func TestCommands_TaskCommand_Bad_MissingRequiredFields(t *testing.T) { @@ -147,8 +145,8 @@ func TestCommands_TaskCommand_Bad_MissingRequiredFields(t *testing.T) { core.Option{Key: "task_identifier", Value: "1"}, )) - assert.False(t, r.OK) - assert.Contains(t, r.Value.(error).Error(), "required") + core.AssertFalse(t, r.OK) + core.AssertContains(t, r.Value.(error).Error(), "required") } func TestCommands_TaskCommand_Ugly_ToggleCriteriaFallback(t *testing.T) { @@ -163,22 +161,22 @@ func TestCommands_TaskCommand_Ugly_ToggleCriteriaFallback(t *testing.T) { {Name: "Setup", Criteria: []string{"Review RFC"}}, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) r := s.cmdTaskToggle(core.NewOptions( core.Option{Key: "plan_slug", Value: plan.Slug}, core.Option{Key: "phase_order", Value: 1}, core.Option{Key: "task_identifier", Value: 1}, )) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(TaskOutput) - require.True(t, ok) - assert.Equal(t, "completed", output.Task.Status) - assert.Equal(t, "Review RFC", output.Task.Title) + core.RequireTrue(t, ok) + core.AssertEqual(t, "completed", output.Task.Status) + core.AssertEqual(t, "Review RFC", output.Task.Title) } func TestCommands_TaskCommand_Bad_CreateMissingTitle(t *testing.T) { @@ -189,6 +187,6 @@ func TestCommands_TaskCommand_Bad_CreateMissingTitle(t *testing.T) { core.Option{Key: "phase_order", Value: 1}, )) - assert.False(t, r.OK) - assert.Contains(t, r.Value.(error).Error(), "required") + core.AssertFalse(t, r.OK) + core.AssertContains(t, r.Value.(error).Error(), "required") } diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index f6d4b729..1c9461f6 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -11,10 +11,8 @@ import ( "testing" "time" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/forge" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // testPrepWithCore creates a PrepSubsystem backed by a real Core + Forge mock. @@ -81,7 +79,7 @@ func captureStdout(t *testing.T, run func()) string { func TestCommandsforge_CmdIssueGet_Bad_MissingArgs(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdIssueGet(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueGet_Good_Success(t *testing.T) { @@ -98,7 +96,7 @@ func TestCommandsforge_CmdIssueGet_Good_Success(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "42"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdIssueGet_Bad_APIError(t *testing.T) { @@ -112,13 +110,13 @@ func TestCommandsforge_CmdIssueGet_Bad_APIError(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "42"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueList_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdIssueList(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueList_Good_Success(t *testing.T) { @@ -132,7 +130,7 @@ func TestCommandsforge_CmdIssueList_Good_Success(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdIssueList(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdIssueList_Good_Empty(t *testing.T) { @@ -143,13 +141,13 @@ func TestCommandsforge_CmdIssueList_Good_Empty(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdIssueList(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdIssueComment_Bad_MissingArgs(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdIssueComment(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueComment_Good_Success(t *testing.T) { @@ -164,13 +162,13 @@ func TestCommandsforge_CmdIssueComment_Good_Success(t *testing.T) { core.Option{Key: "number", Value: "5"}, core.Option{Key: "body", Value: "LGTM"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdIssueCreate_Bad_MissingTitle(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdIssueCreate(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueCreate_Good_Success(t *testing.T) { @@ -188,7 +186,7 @@ func TestCommandsforge_CmdIssueCreate_Good_Success(t *testing.T) { core.Option{Key: "body", Value: "Details here"}, core.Option{Key: "assignee", Value: "virgil"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdIssueCreate_Good_WithLabelsAndMilestone(t *testing.T) { @@ -222,7 +220,7 @@ func TestCommandsforge_CmdIssueCreate_Good_WithLabelsAndMilestone(t *testing.T) core.Option{Key: "milestone", Value: "v0.8.0"}, core.Option{Key: "ref", Value: "dev"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_RegisterCommands_Good_BrainRecall(t *testing.T) { @@ -230,18 +228,18 @@ func TestCommands_RegisterCommands_Good_BrainRecall(t *testing.T) { s.registerCommands(context.Background()) - assert.Contains(t, c.Commands(), "brain/recall") - assert.Contains(t, c.Commands(), "brain:recall") - assert.Contains(t, c.Commands(), "brain/remember") - assert.Contains(t, c.Commands(), "brain:remember") + core.AssertContains(t, c.Commands(), "brain/recall") + core.AssertContains(t, c.Commands(), "brain:recall") + core.AssertContains(t, c.Commands(), "brain/remember") + core.AssertContains(t, c.Commands(), "brain:remember") } func TestCommands_CmdBrainList_Good(t *testing.T) { s, c := testPrepWithCore(t, nil) c.Action("brain.list", func(_ context.Context, options core.Options) core.Result { - assert.Equal(t, "agent", options.String("project")) - assert.Equal(t, "architecture", options.String("type")) - assert.Equal(t, "virgil", options.String("agent_id")) + core.AssertEqual(t, "agent", options.String("project")) + core.AssertEqual(t, "architecture", options.String("type")) + core.AssertEqual(t, "virgil", options.String("agent_id")) return core.Result{Value: map[string]any{ "success": true, "count": 1, @@ -267,26 +265,26 @@ func TestCommands_CmdBrainList_Good(t *testing.T) { core.Option{Key: "type", Value: "architecture"}, core.Option{Key: "agent", Value: "virgil"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "count: 1") - assert.Contains(t, output, "mem-1 architecture") - assert.Contains(t, output, "supersedes: 3") - assert.Contains(t, output, "deleted_at: 2026-03-31T12:30:00Z") - assert.Contains(t, output, "Use named actions.") + core.AssertContains(t, output, "count: 1") + core.AssertContains(t, output, "mem-1 architecture") + core.AssertContains(t, output, "supersedes: 3") + core.AssertContains(t, output, "deleted_at: 2026-03-31T12:30:00Z") + core.AssertContains(t, output, "Use named actions.") } func TestCommands_CmdBrainRemember_Good(t *testing.T) { s, c := testPrepWithCore(t, nil) c.Action("brain.remember", func(_ context.Context, options core.Options) core.Result { - assert.Equal(t, "Use named actions.", options.String("content")) - assert.Equal(t, "convention", options.String("type")) - assert.Equal(t, []string{"architecture", "history"}, optionStringSliceValue(options, "tags")) - assert.Equal(t, "agent", options.String("project")) - assert.Equal(t, "0.9", options.String("confidence")) - assert.Equal(t, "mem-1", options.String("supersedes")) - assert.Equal(t, 24, options.Int("expires_in")) + core.AssertEqual(t, "Use named actions.", options.String("content")) + core.AssertEqual(t, "convention", options.String("type")) + core.AssertEqual(t, []string{"architecture", "history"}, optionStringSliceValue(options, "tags")) + core.AssertEqual(t, "agent", options.String("project")) + core.AssertEqual(t, "0.9", options.String("confidence")) + core.AssertEqual(t, "mem-1", options.String("supersedes")) + core.AssertEqual(t, 24, options.Int("expires_in")) return core.Result{Value: map[string]any{ "success": true, "memory_id": "mem-1", @@ -304,11 +302,11 @@ func TestCommands_CmdBrainRemember_Good(t *testing.T) { core.Option{Key: "supersedes", Value: "mem-1"}, core.Option{Key: "expires_in", Value: 24}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "remembered: mem-1") - assert.Contains(t, output, "timestamp: 2026-03-31T12:00:00Z") + core.AssertContains(t, output, "remembered: mem-1") + core.AssertContains(t, output, "timestamp: 2026-03-31T12:00:00Z") } func TestCommands_CmdBrainRemember_Bad_MissingContent(t *testing.T) { @@ -318,10 +316,10 @@ func TestCommands_CmdBrainRemember_Bad_MissingContent(t *testing.T) { core.Option{Key: "type", Value: "convention"}, )) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "content and type are required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "content and type are required") } func TestCommands_CmdBrainRemember_Ugly_InvalidOutput(t *testing.T) { @@ -335,10 +333,10 @@ func TestCommands_CmdBrainRemember_Ugly_InvalidOutput(t *testing.T) { core.Option{Key: "type", Value: "convention"}, )) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "invalid brain remember output") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "invalid brain remember output") } func TestCommands_CmdBrainList_Bad_MissingAction(t *testing.T) { @@ -346,10 +344,10 @@ func TestCommands_CmdBrainList_Bad_MissingAction(t *testing.T) { result := s.cmdBrainList(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "action not registered") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "action not registered") } func TestCommands_CmdBrainList_Ugly_InvalidOutput(t *testing.T) { @@ -360,21 +358,21 @@ func TestCommands_CmdBrainList_Ugly_InvalidOutput(t *testing.T) { result := s.cmdBrainList(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "invalid brain list output") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "invalid brain list output") } func TestCommands_CmdBrainRecall_Good(t *testing.T) { s, c := testPrepWithCore(t, nil) c.Action("brain.recall", func(_ context.Context, options core.Options) core.Result { - assert.Equal(t, "workspace handoff context", options.String("query")) - assert.Equal(t, 3, options.Int("top_k")) - assert.Equal(t, "agent", options.String("project")) - assert.Equal(t, "architecture", options.String("type")) - assert.Equal(t, "virgil", options.String("agent_id")) - assert.Equal(t, "0.75", options.String("min_confidence")) + core.AssertEqual(t, "workspace handoff context", options.String("query")) + core.AssertEqual(t, 3, options.Int("top_k")) + core.AssertEqual(t, "agent", options.String("project")) + core.AssertEqual(t, "architecture", options.String("type")) + core.AssertEqual(t, "virgil", options.String("agent_id")) + core.AssertEqual(t, "0.75", options.String("min_confidence")) return core.Result{Value: map[string]any{ "success": true, "count": 1, @@ -401,12 +399,12 @@ func TestCommands_CmdBrainRecall_Good(t *testing.T) { core.Option{Key: "agent", Value: "virgil"}, core.Option{Key: "min_confidence", Value: "0.75"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "count: 1") - assert.Contains(t, output, "mem-1 architecture") - assert.Contains(t, output, "Use named actions.") + core.AssertContains(t, output, "count: 1") + core.AssertContains(t, output, "mem-1 architecture") + core.AssertContains(t, output, "Use named actions.") } func TestCommands_CmdBrainRecall_Bad_MissingQuery(t *testing.T) { @@ -414,10 +412,10 @@ func TestCommands_CmdBrainRecall_Bad_MissingQuery(t *testing.T) { result := s.cmdBrainRecall(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "query is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "query is required") } func TestCommands_CmdBrainRecall_Ugly_InvalidOutput(t *testing.T) { @@ -428,17 +426,17 @@ func TestCommands_CmdBrainRecall_Ugly_InvalidOutput(t *testing.T) { result := s.cmdBrainRecall(core.NewOptions(core.Option{Key: "_arg", Value: "workspace handoff context"})) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "invalid brain recall output") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "invalid brain recall output") } func TestCommands_CmdBrainForget_Good(t *testing.T) { s, c := testPrepWithCore(t, nil) c.Action("brain.forget", func(_ context.Context, options core.Options) core.Result { - assert.Equal(t, "mem-1", options.String("id")) - assert.Equal(t, "superseded", options.String("reason")) + core.AssertEqual(t, "mem-1", options.String("id")) + core.AssertEqual(t, "superseded", options.String("reason")) return core.Result{Value: map[string]any{ "success": true, "forgotten": "mem-1", @@ -450,11 +448,11 @@ func TestCommands_CmdBrainForget_Good(t *testing.T) { core.Option{Key: "_arg", Value: "mem-1"}, core.Option{Key: "reason", Value: "superseded"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "forgotten: mem-1") - assert.Contains(t, output, "reason: superseded") + core.AssertContains(t, output, "forgotten: mem-1") + core.AssertContains(t, output, "reason: superseded") } func TestCommands_CmdBrainForget_Bad_MissingID(t *testing.T) { @@ -462,10 +460,10 @@ func TestCommands_CmdBrainForget_Bad_MissingID(t *testing.T) { result := s.cmdBrainForget(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "memory id is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "memory id is required") } func TestCommands_CmdBrainForget_Ugly_ActionFailure(t *testing.T) { @@ -476,10 +474,10 @@ func TestCommands_CmdBrainForget_Ugly_ActionFailure(t *testing.T) { result := s.cmdBrainForget(core.NewOptions(core.Option{Key: "_arg", Value: "mem-1"})) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "failed to forget memory") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "failed to forget memory") } func TestCommandsforge_CmdIssueCreate_Bad_APIError(t *testing.T) { @@ -499,13 +497,13 @@ func TestCommandsforge_CmdIssueCreate_Bad_APIError(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "title", Value: "Fail"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRGet_Bad_MissingArgs(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPRGet(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRGet_Good_Success(t *testing.T) { @@ -523,7 +521,7 @@ func TestCommandsforge_CmdPRGet_Good_Success(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "3"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdPRGet_Bad_APIError(t *testing.T) { @@ -537,7 +535,7 @@ func TestCommandsforge_CmdPRGet_Bad_APIError(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "99"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRList_Good_WithPRs(t *testing.T) { @@ -553,7 +551,7 @@ func TestCommandsforge_CmdPRList_Good_WithPRs(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdPRList(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdPRList_Bad_APIError(t *testing.T) { @@ -564,7 +562,7 @@ func TestCommandsforge_CmdPRList_Bad_APIError(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdPRList(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRMerge_Bad_APIError(t *testing.T) { @@ -579,7 +577,7 @@ func TestCommandsforge_CmdPRMerge_Bad_APIError(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "5"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRMerge_Good_CustomMethod(t *testing.T) { @@ -594,23 +592,23 @@ func TestCommandsforge_CmdPRMerge_Good_CustomMethod(t *testing.T) { core.Option{Key: "number", Value: "5"}, core.Option{Key: "method", Value: "squash"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdPRClose_Bad_MissingArgs(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPRClose(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRClose_Good_Success(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPatch, r.Method) - assert.Equal(t, "/api/v1/repos/core/go-io/pulls/5", r.URL.Path) + core.AssertEqual(t, http.MethodPatch, r.Method) + core.AssertEqual(t, "/api/v1/repos/core/go-io/pulls/5", r.URL.Path) bodyResult := core.ReadAll(r.Body) - assert.True(t, bodyResult.OK) - assert.Contains(t, bodyResult.Value.(string), `"state":"closed"`) + core.AssertTrue(t, bodyResult.OK) + core.AssertContains(t, bodyResult.Value.(string), `"state":"closed"`) w.Write([]byte(core.JSONMarshalString(map[string]any{ "number": 5, @@ -624,7 +622,7 @@ func TestCommandsforge_CmdPRClose_Good_Success(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "5"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdPRClose_Ugly_APIError(t *testing.T) { @@ -638,7 +636,7 @@ func TestCommandsforge_CmdPRClose_Ugly_APIError(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "5"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueGet_Good_WithBody(t *testing.T) { @@ -655,7 +653,7 @@ func TestCommandsforge_CmdIssueGet_Good_WithBody(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "1"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdIssueList_Bad_APIError(t *testing.T) { @@ -666,7 +664,7 @@ func TestCommandsforge_CmdIssueList_Bad_APIError(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdIssueList(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdIssueComment_Bad_APIError(t *testing.T) { @@ -681,7 +679,7 @@ func TestCommandsforge_CmdIssueComment_Bad_APIError(t *testing.T) { core.Option{Key: "number", Value: "1"}, core.Option{Key: "body", Value: "test"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdRepoGet_Bad_APIError(t *testing.T) { @@ -692,7 +690,7 @@ func TestCommandsforge_CmdRepoGet_Bad_APIError(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdRepoGet(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdRepoList_Bad_APIError(t *testing.T) { @@ -703,13 +701,13 @@ func TestCommandsforge_CmdRepoList_Bad_APIError(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdRepoList(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRList_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPRList(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRList_Good_Empty(t *testing.T) { @@ -720,13 +718,13 @@ func TestCommandsforge_CmdPRList_Good_Empty(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdPRList(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdPRMerge_Bad_MissingArgs(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPRMerge(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdPRMerge_Good_DefaultMethod(t *testing.T) { @@ -740,13 +738,13 @@ func TestCommandsforge_CmdPRMerge_Good_DefaultMethod(t *testing.T) { core.Option{Key: "_arg", Value: "go-io"}, core.Option{Key: "number", Value: "5"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdRepoGet_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdRepoGet(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsforge_CmdRepoGet_Good_Success(t *testing.T) { @@ -761,7 +759,7 @@ func TestCommandsforge_CmdRepoGet_Good_Success(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdRepoGet(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsforge_CmdRepoList_Good_Success(t *testing.T) { @@ -775,7 +773,7 @@ func TestCommandsforge_CmdRepoList_Good_Success(t *testing.T) { s, _ := testPrepWithCore(t, srv) r := s.cmdRepoList(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } // --- Workspace command methods --- @@ -783,7 +781,7 @@ func TestCommandsforge_CmdRepoList_Good_Success(t *testing.T) { func TestCommandsworkspace_CmdWorkspaceList_Good_Empty(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdWorkspaceList(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsworkspace_CmdWorkspaceList_Good_WithEntries(t *testing.T) { @@ -795,13 +793,13 @@ func TestCommandsworkspace_CmdWorkspaceList_Good_WithEntries(t *testing.T) { fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(WorkspaceStatus{Status: "running", Repo: "go-io", Agent: "codex"})) r := s.cmdWorkspaceList(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsworkspace_CmdWorkspaceClean_Good_Empty(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdWorkspaceClean(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommandsworkspace_CmdWorkspaceClean_Good_RemovesCompleted(t *testing.T) { @@ -813,9 +811,9 @@ func TestCommandsworkspace_CmdWorkspaceClean_Good_RemovesCompleted(t *testing.T) fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(WorkspaceStatus{Status: "completed", Repo: "go-io", Agent: "codex"})) r := s.cmdWorkspaceClean(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) - assert.False(t, fs.Exists(ws)) + core.AssertFalse(t, fs.Exists(ws)) } func TestCommandsworkspace_CmdWorkspaceClean_Good_FilterFailed(t *testing.T) { @@ -832,10 +830,10 @@ func TestCommandsworkspace_CmdWorkspaceClean_Good_FilterFailed(t *testing.T) { } r := s.cmdWorkspaceClean(core.NewOptions(core.Option{Key: "_arg", Value: "failed"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) - assert.False(t, fs.Exists(core.JoinPath(wsRoot, "ws-bad"))) - assert.True(t, fs.Exists(core.JoinPath(wsRoot, "ws-ok"))) + core.AssertFalse(t, fs.Exists(core.JoinPath(wsRoot, "ws-bad"))) + core.AssertTrue(t, fs.Exists(core.JoinPath(wsRoot, "ws-ok"))) } func TestCommandsworkspace_CmdWorkspaceClean_Good_FilterBlocked(t *testing.T) { @@ -847,21 +845,21 @@ func TestCommandsworkspace_CmdWorkspaceClean_Good_FilterBlocked(t *testing.T) { fs.Write(core.JoinPath(d, "status.json"), core.JSONMarshalString(WorkspaceStatus{Status: "blocked", Repo: "test", Agent: "codex"})) r := s.cmdWorkspaceClean(core.NewOptions(core.Option{Key: "_arg", Value: "blocked"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) - assert.False(t, fs.Exists(d)) + core.AssertFalse(t, fs.Exists(d)) } func TestCommandsworkspace_CmdWorkspaceDispatch_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdWorkspaceDispatch(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsworkspace_CmdWorkspaceDispatch_Bad_MissingTask(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdWorkspaceDispatch(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.False(t, r.OK) // task is required + core.AssertFalse(t, r.OK) // task is required } // --- commands.go extracted methods --- @@ -869,20 +867,20 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Bad_MissingTask(t *testing.T) { func TestCommands_CmdPrep_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPrep(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdPrep_Good_DefaultsToDev(t *testing.T) { s, _ := testPrepWithCore(t, nil) // Will fail (no local clone) but exercises the default branch logic r := s.cmdPrep(core.NewOptions(core.Option{Key: "_arg", Value: "nonexistent-repo"})) - assert.False(t, r.OK) // expected — no local repo + core.AssertFalse(t, r.OK) // expected — no local repo } func TestCommands_CmdStatus_Good_Empty(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdStatus_Good_WithWorkspaces(t *testing.T) { @@ -894,7 +892,7 @@ func TestCommands_CmdStatus_Good_WithWorkspaces(t *testing.T) { fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(WorkspaceStatus{Status: "completed", Repo: "test", Agent: "codex"})) r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdStatus_Good_DeepWorkspace(t *testing.T) { @@ -910,13 +908,13 @@ func TestCommands_CmdStatus_Good_DeepWorkspace(t *testing.T) { output := captureStdout(t, func() { r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "completed") - assert.Contains(t, output, "codex") - assert.Contains(t, output, "go-io") - assert.Contains(t, output, "core/go-io/task-5") + core.AssertContains(t, output, "completed") + core.AssertContains(t, output, "codex") + core.AssertContains(t, output, "go-io") + core.AssertContains(t, output, "core/go-io/task-5") } func TestCommands_CmdStatus_Good_BranchWorkspace(t *testing.T) { @@ -933,11 +931,11 @@ func TestCommands_CmdStatus_Good_BranchWorkspace(t *testing.T) { output := captureStdout(t, func() { r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "completed") - assert.Contains(t, output, "core/go-io/feature/new-ui") + core.AssertContains(t, output, "completed") + core.AssertContains(t, output, "core/go-io/feature/new-ui") } func TestCommands_CmdStatus_Good_BlockedQuestion(t *testing.T) { @@ -954,13 +952,13 @@ func TestCommands_CmdStatus_Good_BlockedQuestion(t *testing.T) { output := captureStdout(t, func() { r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "blocked") - assert.Contains(t, output, "gemini") - assert.Contains(t, output, "go-io") - assert.Contains(t, output, "Which API version?") + core.AssertContains(t, output, "blocked") + core.AssertContains(t, output, "gemini") + core.AssertContains(t, output, "go-io") + core.AssertContains(t, output, "Which API version?") } func TestCommands_CmdStatus_Good_WorkspaceFilter(t *testing.T) { @@ -984,11 +982,11 @@ func TestCommands_CmdStatus_Good_WorkspaceFilter(t *testing.T) { output := captureStdout(t, func() { r := s.cmdStatus(core.NewOptions(core.Option{Key: "workspace", Value: "core/go-io/task-9"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "core/go-io/task-9") - assert.NotContains(t, output, "core/go-log/task-4") + core.AssertContains(t, output, "core/go-io/task-9") + core.AssertNotContains(t, output, "core/go-log/task-4") } func TestCommands_CmdStatus_Good_StatusFilterAndLimit(t *testing.T) { @@ -1017,24 +1015,24 @@ func TestCommands_CmdStatus_Good_StatusFilterAndLimit(t *testing.T) { core.Option{Key: "status", Value: "blocked"}, core.Option{Key: "limit", Value: 1}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "ws-1") - assert.NotContains(t, output, "ws-2") - assert.NotContains(t, output, "ws-3") + core.AssertContains(t, output, "ws-1") + core.AssertNotContains(t, output, "ws-2") + core.AssertNotContains(t, output, "ws-3") } func TestCommands_CmdPrompt_Bad_MissingRepo(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPrompt(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdPrompt_Good_DefaultTask(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPrompt(core.NewOptions(core.Option{Key: "_arg", Value: "go-io"})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdPrompt_Good_PlanTemplateVariables(t *testing.T) { @@ -1053,23 +1051,23 @@ func TestCommands_CmdPrompt_Good_PlanTemplateVariables(t *testing.T) { core.Option{Key: "plan_template", Value: "new-feature"}, core.Option{Key: "variables", Value: `{"feature_name":"Authentication"}`}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "PLAN:") - assert.Contains(t, output, "Authentication") + core.AssertContains(t, output, "PLAN:") + core.AssertContains(t, output, "Authentication") } func TestCommands_CmdGenerate_Bad_MissingPrompt(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdGenerate(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdGenerate_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "/v1/content/generate", r.URL.Path) - assert.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/content/generate", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) _, _ = w.Write([]byte(`{"data":{"id":"gen_1","provider":"claude","model":"claude-3.7-sonnet","content":"Release notes draft","input_tokens":12,"output_tokens":48,"status":"completed"}}`)) })) defer server.Close() @@ -1080,28 +1078,28 @@ func TestCommands_CmdGenerate_Good(t *testing.T) { core.Option{Key: "_arg", Value: "Draft a release note"}, core.Option{Key: "provider", Value: "claude"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "provider: claude") - assert.Contains(t, output, "model: claude-3.7-sonnet") - assert.Contains(t, output, "status: completed") - assert.Contains(t, output, "content: Release notes draft") + core.AssertContains(t, output, "provider: claude") + core.AssertContains(t, output, "model: claude-3.7-sonnet") + core.AssertContains(t, output, "status: completed") + core.AssertContains(t, output, "content: Release notes draft") } func TestCommands_CmdGenerate_Good_BriefTemplate(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "/v1/content/generate", r.URL.Path) - assert.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/content/generate", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "brief_1", payload["brief_id"]) - assert.Equal(t, "help-article", payload["template"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "brief_1", payload["brief_id"]) + core.AssertEqual(t, "help-article", payload["template"]) _, _ = w.Write([]byte(`{"data":{"id":"gen_2","provider":"claude","model":"claude-3.7-sonnet","content":"Template draft","status":"completed"}}`)) })) @@ -1114,13 +1112,13 @@ func TestCommands_CmdGenerate_Good_BriefTemplate(t *testing.T) { core.Option{Key: "template", Value: "help-article"}, core.Option{Key: "provider", Value: "claude"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "provider: claude") - assert.Contains(t, output, "model: claude-3.7-sonnet") - assert.Contains(t, output, "status: completed") - assert.Contains(t, output, "content: Template draft") + core.AssertContains(t, output, "provider: claude") + core.AssertContains(t, output, "model: claude-3.7-sonnet") + core.AssertContains(t, output, "status: completed") + core.AssertContains(t, output, "content: Template draft") } func TestCommands_CmdContentSchemaGenerate_Good(t *testing.T) { @@ -1133,12 +1131,12 @@ func TestCommands_CmdContentSchemaGenerate_Good(t *testing.T) { core.Option{Key: "url", Value: "https://example.test/workspace"}, core.Option{Key: "steps", Value: `[{"name":"Clone","text":"Clone the repository."},{"name":"Prepare","text":"Build the prompt."}]`}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "schema type: HowTo") - assert.Contains(t, output, `"@type":"HowTo"`) - assert.Contains(t, output, `"name":"Set up the workspace"`) + core.AssertContains(t, output, "schema type: HowTo") + core.AssertContains(t, output, `"@type":"HowTo"`) + core.AssertContains(t, output, `"name":"Set up the workspace"`) } func TestCommands_CmdContentSchemaGenerate_Bad_MissingTitle(t *testing.T) { @@ -1146,7 +1144,7 @@ func TestCommands_CmdContentSchemaGenerate_Bad_MissingTitle(t *testing.T) { r := s.cmdContentSchemaGenerate(core.NewOptions( core.Option{Key: "type", Value: "howto"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdContentSchemaGenerate_Ugly_InvalidSchemaType(t *testing.T) { @@ -1155,7 +1153,7 @@ func TestCommands_CmdContentSchemaGenerate_Ugly_InvalidSchemaType(t *testing.T) core.Option{Key: "type", Value: "toast"}, core.Option{Key: "title", Value: "Set up the workspace"}, )) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdComplete_Good(t *testing.T) { @@ -1174,14 +1172,14 @@ func TestCommands_CmdComplete_Good(t *testing.T) { r := s.cmdComplete(core.NewOptions( core.Option{Key: "workspace", Value: core.JoinPath(WorkspaceRoot(), "core/go-io/task-42")}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdComplete_Bad_MissingTask(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdComplete(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdScan_Good(t *testing.T) { @@ -1201,12 +1199,12 @@ func TestCommands_CmdScan_Good(t *testing.T) { core.Option{Key: "labels", Value: "agentic,bug"}, core.Option{Key: "limit", Value: 5}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "count:") - assert.Contains(t, output, "go-io#10") - assert.Contains(t, output, "Add missing tests") + core.AssertContains(t, output, "count:") + core.AssertContains(t, output, "go-io#10") + core.AssertContains(t, output, "Add missing tests") } func TestCommands_CmdScan_Bad_NoForgeToken(t *testing.T) { @@ -1214,7 +1212,7 @@ func TestCommands_CmdScan_Bad_NoForgeToken(t *testing.T) { s.forgeToken = "" r := s.cmdScan(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdScan_Ugly_EmptyResults(t *testing.T) { @@ -1243,10 +1241,10 @@ func TestCommands_CmdScan_Ugly_EmptyResults(t *testing.T) { core.Option{Key: "org", Value: "core"}, core.Option{Key: "limit", Value: 1}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "count: 0") + core.AssertContains(t, output, "count: 0") } func TestCommands_CmdPlanCreate_Good(t *testing.T) { @@ -1258,20 +1256,20 @@ func TestCommands_CmdPlanCreate_Good(t *testing.T) { core.Option{Key: "objective", Value: "Use Core.Process everywhere"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) output, ok := r.Value.(PlanCreateOutput) - require.True(t, ok) - require.NotEmpty(t, output.ID) - require.NotEmpty(t, output.Path) - assert.True(t, fs.Exists(output.Path)) + core.RequireTrue(t, ok) + core.RequireNotEmpty(t, output.ID) + core.RequireNotEmpty(t, output.Path) + core.AssertTrue(t, fs.Exists(output.Path)) plan, err := readPlan(PlansRoot(), output.ID) - require.NoError(t, err) - assert.Equal(t, "Migrate Core", plan.Title) - assert.Equal(t, "Use Core.Process everywhere", plan.Objective) - assert.Equal(t, "draft", plan.Status) - assert.Equal(t, "migrate-core", plan.Slug) + core.RequireNoError(t, err) + core.AssertEqual(t, "Migrate Core", plan.Title) + core.AssertEqual(t, "Use Core.Process everywhere", plan.Objective) + core.AssertEqual(t, "draft", plan.Status) + core.AssertEqual(t, "migrate-core", plan.Slug) } func TestCommands_CmdPlanStatus_Good_GetAndSet(t *testing.T) { @@ -1281,28 +1279,28 @@ func TestCommands_CmdPlanStatus_Good_GetAndSet(t *testing.T) { Title: "Status Plan", Objective: "Exercise plan status management", }) - require.NoError(t, err) + core.RequireNoError(t, err) getOutput := captureStdout(t, func() { r := s.cmdPlanStatus(core.NewOptions(core.Option{Key: "_arg", Value: created.ID})) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, getOutput, "status:") - assert.Contains(t, getOutput, "draft") + core.AssertContains(t, getOutput, "status:") + core.AssertContains(t, getOutput, "draft") setOutput := captureStdout(t, func() { r := s.cmdPlanStatus(core.NewOptions( core.Option{Key: "_arg", Value: created.ID}, core.Option{Key: "set", Value: "ready"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, setOutput, "status:") - assert.Contains(t, setOutput, "ready") + core.AssertContains(t, setOutput, "status:") + core.AssertContains(t, setOutput, "ready") plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) - assert.Equal(t, "ready", plan.Status) + core.RequireNoError(t, err) + core.AssertEqual(t, "ready", plan.Status) } func TestCommands_CmdPlanArchive_Good(t *testing.T) { @@ -1312,21 +1310,21 @@ func TestCommands_CmdPlanArchive_Good(t *testing.T) { Title: "Archive Plan", Objective: "Exercise archive command", }) - require.NoError(t, err) + core.RequireNoError(t, err) output := captureStdout(t, func() { r := s.cmdPlanArchive(core.NewOptions( core.Option{Key: "_arg", Value: created.ID}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "archived:") + core.AssertContains(t, output, "archived:") plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) - assert.Equal(t, "archived", plan.Status) - assert.False(t, plan.ArchivedAt.IsZero()) + core.RequireNoError(t, err) + core.AssertEqual(t, "archived", plan.Status) + core.AssertFalse(t, plan.ArchivedAt.IsZero()) } func TestCommands_CmdPlanDelete_Good(t *testing.T) { @@ -1336,22 +1334,22 @@ func TestCommands_CmdPlanDelete_Good(t *testing.T) { Title: "Delete Plan", Objective: "Exercise delete command", }) - require.NoError(t, err) + core.RequireNoError(t, err) output := captureStdout(t, func() { r := s.cmdPlanDelete(core.NewOptions( core.Option{Key: "_arg", Value: created.ID}, core.Option{Key: "reason", Value: "RFC contract says soft delete"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "deleted:") + core.AssertContains(t, output, "deleted:") - assert.False(t, fs.Exists(created.Path)) + core.AssertFalse(t, fs.Exists(created.Path)) _, readErr := readPlan(PlansRoot(), created.ID) - require.Error(t, readErr) + core.AssertError(t, readErr) } func TestCommands_CmdExtract_Good(t *testing.T) { @@ -1361,7 +1359,7 @@ func TestCommands_CmdExtract_Good(t *testing.T) { core.Option{Key: "_arg", Value: "default"}, core.Option{Key: "target", Value: target}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdExtract_Good_FromAgentOutput(t *testing.T) { @@ -1369,32 +1367,32 @@ func TestCommands_CmdExtract_Good_FromAgentOutput(t *testing.T) { dir := t.TempDir() source := core.JoinPath(dir, "agent-output.md") target := core.JoinPath(dir, "extracted.json") - require.True(t, fs.Write(source, "Agent run complete.\n\n```json\n{\"summary\":\"done\",\"findings\":2}\n```\n").OK) + core.RequireTrue(t, fs.Write(source, "Agent run complete.\n\n```json\n{\"summary\":\"done\",\"findings\":2}\n```\n").OK) output := captureStdout(t, func() { r := s.cmdExtract(core.NewOptions( core.Option{Key: "source", Value: source}, core.Option{Key: "target", Value: target}, )) - assert.True(t, r.OK) - assert.Equal(t, "{\"summary\":\"done\",\"findings\":2}", r.Value) + core.AssertTrue(t, r.OK) + core.AssertEqual(t, "{\"summary\":\"done\",\"findings\":2}", r.Value) }) - assert.Contains(t, output, "written: ") - assert.True(t, fs.Exists(target)) + core.AssertContains(t, output, "written: ") + core.AssertTrue(t, fs.Exists(target)) written := fs.Read(target) - require.True(t, written.OK) - assert.Equal(t, "{\"summary\":\"done\",\"findings\":2}", written.Value) + core.RequireTrue(t, written.OK) + core.AssertEqual(t, "{\"summary\":\"done\",\"findings\":2}", written.Value) } func TestCommands_CmdExtract_Bad_NoExtractableContent(t *testing.T) { s, _ := testPrepWithCore(t, nil) dir := t.TempDir() source := core.JoinPath(dir, "agent-output.md") - require.True(t, fs.Write(source, "Agent run complete.\nNothing structured here.\n").OK) + core.RequireTrue(t, fs.Write(source, "Agent run complete.\nNothing structured here.\n").OK) r := s.cmdExtract(core.NewOptions(core.Option{Key: "source", Value: source})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdRunTask_Bad_MissingArgs(t *testing.T) { @@ -1403,7 +1401,7 @@ func TestCommands_CmdRunTask_Bad_MissingArgs(t *testing.T) { defer cancel() s.startupContext = ctx r := s.cmdRunTask(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdDispatchSync_Bad_MissingArgs(t *testing.T) { @@ -1412,7 +1410,7 @@ func TestCommands_CmdDispatchSync_Bad_MissingArgs(t *testing.T) { defer cancel() s.startupContext = ctx r := s.cmdDispatchSync(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdRunTask_Bad_MissingTask(t *testing.T) { @@ -1421,7 +1419,7 @@ func TestCommands_CmdRunTask_Bad_MissingTask(t *testing.T) { defer cancel() s.startupContext = ctx r := s.cmdRunTask(core.NewOptions(core.Option{Key: "repo", Value: "go-io"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdOrchestrator_Good_CancelledCtx(t *testing.T) { @@ -1430,7 +1428,7 @@ func TestCommands_CmdOrchestrator_Good_CancelledCtx(t *testing.T) { cancel() // cancel immediately s.startupContext = ctx r := s.cmdOrchestrator(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdDispatch_Good_CancelledCtx(t *testing.T) { @@ -1439,7 +1437,7 @@ func TestCommands_CmdDispatch_Good_CancelledCtx(t *testing.T) { cancel() s.startupContext = ctx r := s.cmdDispatch(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdDispatchStart_Good(t *testing.T) { @@ -1452,11 +1450,11 @@ func TestCommands_CmdDispatchStart_Good(t *testing.T) { output := captureStdout(t, func() { r := s.cmdDispatchStart(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.True(t, called) - assert.Contains(t, output, "dispatch started") + core.AssertTrue(t, called) + core.AssertContains(t, output, "dispatch started") } func TestCommands_CmdDispatchShutdown_Good(t *testing.T) { @@ -1469,11 +1467,11 @@ func TestCommands_CmdDispatchShutdown_Good(t *testing.T) { output := captureStdout(t, func() { r := s.cmdDispatchShutdown(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.True(t, called) - assert.Contains(t, output, "queue frozen") + core.AssertTrue(t, called) + core.AssertContains(t, output, "queue frozen") } func TestCommands_CmdDispatchShutdownNow_Good(t *testing.T) { @@ -1486,11 +1484,11 @@ func TestCommands_CmdDispatchShutdownNow_Good(t *testing.T) { output := captureStdout(t, func() { r := s.cmdDispatchShutdownNow(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.True(t, called) - assert.Contains(t, output, "killed all agents") + core.AssertTrue(t, called) + core.AssertContains(t, output, "killed all agents") } func TestCommands_CmdPoke_Good(t *testing.T) { @@ -1503,19 +1501,19 @@ func TestCommands_CmdPoke_Good(t *testing.T) { output := captureStdout(t, func() { r := s.cmdPoke(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.True(t, called) - assert.Contains(t, output, "queue poke requested") + core.AssertTrue(t, called) + core.AssertContains(t, output, "queue poke requested") } func TestCommands_ParseIntStr_Good(t *testing.T) { - assert.Equal(t, 42, parseIntString("42")) - assert.Equal(t, 123, parseIntString("issue-123")) - assert.Equal(t, 0, parseIntString("")) - assert.Equal(t, 0, parseIntString("abc")) - assert.Equal(t, 7, parseIntString("#7")) + core.AssertEqual(t, 42, parseIntString("42")) + core.AssertEqual(t, 123, parseIntString("issue-123")) + core.AssertEqual(t, 0, parseIntString("")) + core.AssertEqual(t, 0, parseIntString("abc")) + core.AssertEqual(t, 7, parseIntString("#7")) } // --- Registration verification --- @@ -1527,128 +1525,128 @@ func TestCommands_RegisterCommands_Good_AllRegistered(t *testing.T) { s.registerCommands(ctx) cmds := c.Commands() - assert.Contains(t, cmds, "run/task") - assert.Contains(t, cmds, "agentic:run/task") - assert.Contains(t, cmds, "run/flow") - assert.Contains(t, cmds, "agentic:run/flow") - assert.Contains(t, cmds, "flow/preview") - assert.Contains(t, cmds, "agentic:flow/preview") - assert.Contains(t, cmds, "dispatch/sync") - assert.Contains(t, cmds, "agentic:dispatch/sync") - assert.Contains(t, cmds, "run/orchestrator") - assert.Contains(t, cmds, "agentic:run/orchestrator") - assert.Contains(t, cmds, "dispatch") - assert.Contains(t, cmds, "agentic:dispatch") - assert.Contains(t, cmds, "dispatch/start") - assert.Contains(t, cmds, "agentic:dispatch/start") - assert.Contains(t, cmds, "dispatch/shutdown") - assert.Contains(t, cmds, "agentic:dispatch/shutdown") - assert.Contains(t, cmds, "dispatch/shutdown-now") - assert.Contains(t, cmds, "agentic:dispatch/shutdown-now") - assert.Contains(t, cmds, "poke") - assert.Contains(t, cmds, "agentic:poke") - assert.Contains(t, cmds, "prep") - assert.Contains(t, cmds, "agentic:prep-workspace") - assert.Contains(t, cmds, "resume") - assert.Contains(t, cmds, "agentic:resume") - assert.Contains(t, cmds, "content/generate") - assert.Contains(t, cmds, "agentic:content/generate") - assert.Contains(t, cmds, "content/schema/generate") - assert.Contains(t, cmds, "agentic:content/schema/generate") - assert.Contains(t, cmds, "complete") - assert.Contains(t, cmds, "agentic:complete") - assert.Contains(t, cmds, "scan") - assert.Contains(t, cmds, "agentic:scan") - assert.Contains(t, cmds, "mirror") - assert.Contains(t, cmds, "agentic:mirror") - assert.Contains(t, cmds, "brain/ingest") - assert.Contains(t, cmds, "brain:ingest") - assert.Contains(t, cmds, "brain/seed-memory") - assert.Contains(t, cmds, "brain:seed-memory") - assert.Contains(t, cmds, "brain/list") - assert.Contains(t, cmds, "brain:list") - assert.Contains(t, cmds, "brain/forget") - assert.Contains(t, cmds, "brain:forget") - assert.Contains(t, cmds, "status") - assert.Contains(t, cmds, "agentic:status") - assert.Contains(t, cmds, "prompt") - assert.Contains(t, cmds, "agentic:prompt") - assert.Contains(t, cmds, "prompt_version") - assert.Contains(t, cmds, "agentic:prompt_version") - assert.Contains(t, cmds, "prompt/version") - assert.Contains(t, cmds, "extract") - assert.Contains(t, cmds, "agentic:extract") - assert.Contains(t, cmds, "lang/detect") - assert.Contains(t, cmds, "agentic:lang/detect") - assert.Contains(t, cmds, "lang/list") - assert.Contains(t, cmds, "agentic:lang/list") - assert.Contains(t, cmds, "epic") - assert.Contains(t, cmds, "agentic:epic") - assert.Contains(t, cmds, "plan") - assert.Contains(t, cmds, "agentic:plan/create") - assert.Contains(t, cmds, "plan/create") - assert.Contains(t, cmds, "agentic:plan/list") - assert.Contains(t, cmds, "plan/list") - assert.Contains(t, cmds, "agentic:plan/read") - assert.Contains(t, cmds, "plan/read") - assert.Contains(t, cmds, "agentic:plan/show") - assert.Contains(t, cmds, "plan/show") - assert.Contains(t, cmds, "agentic:plan/status") - assert.Contains(t, cmds, "plan/update") - assert.Contains(t, cmds, "agentic:plan/update") - assert.Contains(t, cmds, "agentic:plan/check") - assert.Contains(t, cmds, "plan/status") - assert.Contains(t, cmds, "plan/check") - assert.Contains(t, cmds, "agentic:plan/archive") - assert.Contains(t, cmds, "plan/archive") - assert.Contains(t, cmds, "agentic:plan/delete") - assert.Contains(t, cmds, "plan/delete") - assert.Contains(t, cmds, "agentic:plan-cleanup") - assert.Contains(t, cmds, "commit") - assert.Contains(t, cmds, "agentic:commit") - assert.Contains(t, cmds, "session/start") - assert.Contains(t, cmds, "session/get") - assert.Contains(t, cmds, "agentic:session/get") - assert.Contains(t, cmds, "session/list") - assert.Contains(t, cmds, "agentic:session/list") - assert.Contains(t, cmds, "agentic:session/start") - assert.Contains(t, cmds, "session/continue") - assert.Contains(t, cmds, "agentic:session/continue") - assert.Contains(t, cmds, "session/end") - assert.Contains(t, cmds, "agentic:session/end") - assert.Contains(t, cmds, "pr-manage") - assert.Contains(t, cmds, "agentic:pr-manage") - assert.Contains(t, cmds, "review-queue") - assert.Contains(t, cmds, "agentic:review-queue") - assert.Contains(t, cmds, "task") - assert.Contains(t, cmds, "phase") - assert.Contains(t, cmds, "agentic:phase") - assert.Contains(t, cmds, "phase/get") - assert.Contains(t, cmds, "agentic:phase/get") - assert.Contains(t, cmds, "phase/update_status") - assert.Contains(t, cmds, "agentic:phase/update_status") - assert.Contains(t, cmds, "phase/add_checkpoint") - assert.Contains(t, cmds, "agentic:phase/add_checkpoint") - assert.Contains(t, cmds, "task/update") - assert.Contains(t, cmds, "task/toggle") - assert.Contains(t, cmds, "sprint") - assert.Contains(t, cmds, "sprint/create") + core.AssertContains(t, cmds, "run/task") + core.AssertContains(t, cmds, "agentic:run/task") + core.AssertContains(t, cmds, "run/flow") + core.AssertContains(t, cmds, "agentic:run/flow") + core.AssertContains(t, cmds, "flow/preview") + core.AssertContains(t, cmds, "agentic:flow/preview") + core.AssertContains(t, cmds, "dispatch/sync") + core.AssertContains(t, cmds, "agentic:dispatch/sync") + core.AssertContains(t, cmds, "run/orchestrator") + core.AssertContains(t, cmds, "agentic:run/orchestrator") + core.AssertContains(t, cmds, "dispatch") + core.AssertContains(t, cmds, "agentic:dispatch") + core.AssertContains(t, cmds, "dispatch/start") + core.AssertContains(t, cmds, "agentic:dispatch/start") + core.AssertContains(t, cmds, "dispatch/shutdown") + core.AssertContains(t, cmds, "agentic:dispatch/shutdown") + core.AssertContains(t, cmds, "dispatch/shutdown-now") + core.AssertContains(t, cmds, "agentic:dispatch/shutdown-now") + core.AssertContains(t, cmds, "poke") + core.AssertContains(t, cmds, "agentic:poke") + core.AssertContains(t, cmds, "prep") + core.AssertContains(t, cmds, "agentic:prep-workspace") + core.AssertContains(t, cmds, "resume") + core.AssertContains(t, cmds, "agentic:resume") + core.AssertContains(t, cmds, "content/generate") + core.AssertContains(t, cmds, "agentic:content/generate") + core.AssertContains(t, cmds, "content/schema/generate") + core.AssertContains(t, cmds, "agentic:content/schema/generate") + core.AssertContains(t, cmds, "complete") + core.AssertContains(t, cmds, "agentic:complete") + core.AssertContains(t, cmds, "scan") + core.AssertContains(t, cmds, "agentic:scan") + core.AssertContains(t, cmds, "mirror") + core.AssertContains(t, cmds, "agentic:mirror") + core.AssertContains(t, cmds, "brain/ingest") + core.AssertContains(t, cmds, "brain:ingest") + core.AssertContains(t, cmds, "brain/seed-memory") + core.AssertContains(t, cmds, "brain:seed-memory") + core.AssertContains(t, cmds, "brain/list") + core.AssertContains(t, cmds, "brain:list") + core.AssertContains(t, cmds, "brain/forget") + core.AssertContains(t, cmds, "brain:forget") + core.AssertContains(t, cmds, "status") + core.AssertContains(t, cmds, "agentic:status") + core.AssertContains(t, cmds, "prompt") + core.AssertContains(t, cmds, "agentic:prompt") + core.AssertContains(t, cmds, "prompt_version") + core.AssertContains(t, cmds, "agentic:prompt_version") + core.AssertContains(t, cmds, "prompt/version") + core.AssertContains(t, cmds, "extract") + core.AssertContains(t, cmds, "agentic:extract") + core.AssertContains(t, cmds, "lang/detect") + core.AssertContains(t, cmds, "agentic:lang/detect") + core.AssertContains(t, cmds, "lang/list") + core.AssertContains(t, cmds, "agentic:lang/list") + core.AssertContains(t, cmds, "epic") + core.AssertContains(t, cmds, "agentic:epic") + core.AssertContains(t, cmds, "plan") + core.AssertContains(t, cmds, "agentic:plan/create") + core.AssertContains(t, cmds, "plan/create") + core.AssertContains(t, cmds, "agentic:plan/list") + core.AssertContains(t, cmds, "plan/list") + core.AssertContains(t, cmds, "agentic:plan/read") + core.AssertContains(t, cmds, "plan/read") + core.AssertContains(t, cmds, "agentic:plan/show") + core.AssertContains(t, cmds, "plan/show") + core.AssertContains(t, cmds, "agentic:plan/status") + core.AssertContains(t, cmds, "plan/update") + core.AssertContains(t, cmds, "agentic:plan/update") + core.AssertContains(t, cmds, "agentic:plan/check") + core.AssertContains(t, cmds, "plan/status") + core.AssertContains(t, cmds, "plan/check") + core.AssertContains(t, cmds, "agentic:plan/archive") + core.AssertContains(t, cmds, "plan/archive") + core.AssertContains(t, cmds, "agentic:plan/delete") + core.AssertContains(t, cmds, "plan/delete") + core.AssertContains(t, cmds, "agentic:plan-cleanup") + core.AssertContains(t, cmds, "commit") + core.AssertContains(t, cmds, "agentic:commit") + core.AssertContains(t, cmds, "session/start") + core.AssertContains(t, cmds, "session/get") + core.AssertContains(t, cmds, "agentic:session/get") + core.AssertContains(t, cmds, "session/list") + core.AssertContains(t, cmds, "agentic:session/list") + core.AssertContains(t, cmds, "agentic:session/start") + core.AssertContains(t, cmds, "session/continue") + core.AssertContains(t, cmds, "agentic:session/continue") + core.AssertContains(t, cmds, "session/end") + core.AssertContains(t, cmds, "agentic:session/end") + core.AssertContains(t, cmds, "pr-manage") + core.AssertContains(t, cmds, "agentic:pr-manage") + core.AssertContains(t, cmds, "review-queue") + core.AssertContains(t, cmds, "agentic:review-queue") + core.AssertContains(t, cmds, "task") + core.AssertContains(t, cmds, "phase") + core.AssertContains(t, cmds, "agentic:phase") + core.AssertContains(t, cmds, "phase/get") + core.AssertContains(t, cmds, "agentic:phase/get") + core.AssertContains(t, cmds, "phase/update_status") + core.AssertContains(t, cmds, "agentic:phase/update_status") + core.AssertContains(t, cmds, "phase/add_checkpoint") + core.AssertContains(t, cmds, "agentic:phase/add_checkpoint") + core.AssertContains(t, cmds, "task/update") + core.AssertContains(t, cmds, "task/toggle") + core.AssertContains(t, cmds, "sprint") + core.AssertContains(t, cmds, "sprint/create") } func TestCommands_CmdPRManage_Good_NoCandidates(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdPRManage(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdMirror_Good_NoRepos(t *testing.T) { s, _ := testPrepWithCore(t, nil) output := captureStdout(t, func() { r := s.cmdMirror(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) }) - assert.Contains(t, output, "count: 0") + core.AssertContains(t, output, "count: 0") } // --- CmdExtract Bad/Ugly --- @@ -1663,7 +1661,7 @@ func TestCommands_CmdExtract_Bad_TargetDirAlreadyHasFiles(t *testing.T) { r := s.cmdExtract(core.NewOptions( core.Option{Key: "target", Value: target}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } func TestCommands_CmdExtract_Ugly_TargetIsFile(t *testing.T) { @@ -1676,7 +1674,7 @@ func TestCommands_CmdExtract_Ugly_TargetIsFile(t *testing.T) { core.Option{Key: "target", Value: target}, )) // Extraction should fail because target is a file, not a directory - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } // --- CmdOrchestrator Bad/Ugly --- @@ -1687,7 +1685,7 @@ func TestCommands_CmdOrchestrator_Bad_DoneContext(t *testing.T) { defer cancel() s.startupContext = ctx r := s.cmdOrchestrator(core.NewOptions()) - assert.True(t, r.OK) // returns OK after ctx.Done() + core.AssertTrue(t, r.OK) // returns OK after ctx.Done() } func TestCommands_CmdOrchestrator_Ugly_CancelledImmediately(t *testing.T) { @@ -1696,7 +1694,7 @@ func TestCommands_CmdOrchestrator_Ugly_CancelledImmediately(t *testing.T) { cancel() s.startupContext = ctx r := s.cmdOrchestrator(core.NewOptions()) - assert.True(t, r.OK) // exits immediately when context is already cancelled + core.AssertTrue(t, r.OK) // exits immediately when context is already cancelled } // --- CmdPrep Ugly --- @@ -1717,7 +1715,7 @@ func TestCommands_CmdPrep_Ugly_AllOptionalFields(t *testing.T) { core.Option{Key: "dry-run", Value: "true"}, )) // Will fail (no local clone) but exercises all option parsing paths - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } // --- CmdPrompt Ugly --- @@ -1731,7 +1729,7 @@ func TestCommands_CmdPrompt_Ugly_AllOptionalFields(t *testing.T) { core.Option{Key: "template", Value: "verify"}, core.Option{Key: "persona", Value: "engineering/security"}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } // --- CmdRunTask Good/Ugly --- @@ -1747,7 +1745,7 @@ func TestCommands_CmdRunTask_Good_DefaultsApplied(t *testing.T) { core.Option{Key: "task", Value: "run all tests"}, )) // Will fail on dispatch but exercises the default-filling path - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommands_CmdRunTask_Ugly_MixedIssueString(t *testing.T) { @@ -1761,7 +1759,7 @@ func TestCommands_CmdRunTask_Ugly_MixedIssueString(t *testing.T) { core.Option{Key: "issue", Value: "issue-42abc"}, )) // Will fail on dispatch but exercises parseIntString with mixed chars - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } // --- CommandContext Good/Bad/Ugly --- @@ -1771,13 +1769,13 @@ func TestCommands_CommandContext_Good_StoredStartupContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() s.registerCommands(ctx) - assert.Same(t, ctx, s.commandContext()) + core.AssertSame(t, ctx, s.commandContext()) } func TestCommands_CommandContext_Bad_FallsBackToBackground(t *testing.T) { s, _ := testPrepWithCore(t, nil) - assert.NotNil(t, s.commandContext()) - assert.NoError(t, s.commandContext().Err()) + core.AssertNotNil(t, s.commandContext()) + core.AssertNoError(t, s.commandContext().Err()) } func TestCommands_CommandContext_Ugly_CancelledStartupContext(t *testing.T) { @@ -1786,7 +1784,7 @@ func TestCommands_CommandContext_Ugly_CancelledStartupContext(t *testing.T) { cancel() // pre-cancelled s.startupContext = ctx r := s.cmdOrchestrator(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } // --- CmdStatus Bad/Ugly --- @@ -1804,7 +1802,7 @@ func TestCommands_CmdStatus_Bad_NoWorkspaceDir(t *testing.T) { } r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) // returns OK with "no workspaces found" + core.AssertTrue(t, r.OK) // returns OK with "no workspaces found" } func TestCommands_CmdStatus_Ugly_NonDirEntries(t *testing.T) { @@ -1821,22 +1819,22 @@ func TestCommands_CmdStatus_Ugly_NonDirEntries(t *testing.T) { fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(WorkspaceStatus{Status: "running", Repo: "test", Agent: "codex"})) r := s.cmdStatus(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) } // --- ParseIntStr Bad/Ugly --- func TestCommands_ParseIntStr_Bad_NegativeAndOverflow(t *testing.T) { // parseIntString extracts digits only, ignoring minus signs - assert.Equal(t, 5, parseIntString("-5")) // extracts "5", ignores "-" - assert.Equal(t, 0, parseIntString("-")) // no digits - assert.Equal(t, 0, parseIntString("---")) // no digits + core.AssertEqual(t, 5, parseIntString("-5")) // extracts "5", ignores "-" + core.AssertEqual(t, 0, parseIntString("-")) // no digits + core.AssertEqual(t, 0, parseIntString("---")) // no digits } func TestCommands_ParseIntStr_Ugly_UnicodeAndMixed(t *testing.T) { // Unicode digits (e.g. Arabic-Indic) are NOT ASCII 0-9 so ignored - assert.Equal(t, 0, parseIntString("\u0661\u0662\u0663")) // ١٢٣ — not ASCII digits - assert.Equal(t, 42, parseIntString("abc42xyz")) // mixed chars - assert.Equal(t, 123, parseIntString("1a2b3c")) // interleaved - assert.Equal(t, 0, parseIntString(" \t\n")) // whitespace only + core.AssertEqual(t, 0, parseIntString("\u0661\u0662\u0663")) // ١٢٣ — not ASCII digits + core.AssertEqual(t, 42, parseIntString("abc42xyz")) // mixed chars + core.AssertEqual(t, 123, parseIntString("1a2b3c")) // interleaved + core.AssertEqual(t, 0, parseIntString(" \t\n")) // whitespace only } diff --git a/pkg/agentic/commands_workspace.go b/pkg/agentic/commands_workspace.go index 1171fd02..e6bdf134 100644 --- a/pkg/agentic/commands_workspace.go +++ b/pkg/agentic/commands_workspace.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerWorkspaceCommands() { diff --git a/pkg/agentic/commands_workspace_example_test.go b/pkg/agentic/commands_workspace_example_test.go index 6ad9f3c0..f2b5a728 100644 --- a/pkg/agentic/commands_workspace_example_test.go +++ b/pkg/agentic/commands_workspace_example_test.go @@ -5,7 +5,7 @@ package agentic import ( "os" - core "dappco.re/go/core" + core "dappco.re/go" ) func ExamplePrepSubsystem_cmdWorkspaceClean() { diff --git a/pkg/agentic/commands_workspace_test.go b/pkg/agentic/commands_workspace_test.go index d8e7bba0..f484c3b6 100644 --- a/pkg/agentic/commands_workspace_test.go +++ b/pkg/agentic/commands_workspace_test.go @@ -6,8 +6,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" + core "dappco.re/go" ) func TestCommandsworkspace_RegisterWorkspaceCommands_Good_Aliases(t *testing.T) { @@ -15,14 +14,14 @@ func TestCommandsworkspace_RegisterWorkspaceCommands_Good_Aliases(t *testing.T) s.registerWorkspaceCommands() - assert.Contains(t, c.Commands(), "workspace/list") - assert.Contains(t, c.Commands(), "agentic:workspace/list") - assert.Contains(t, c.Commands(), "workspace/clean") - assert.Contains(t, c.Commands(), "agentic:workspace/clean") - assert.Contains(t, c.Commands(), "workspace/dispatch") - assert.Contains(t, c.Commands(), "agentic:workspace/dispatch") - assert.Contains(t, c.Commands(), "workspace/watch") - assert.Contains(t, c.Commands(), "agentic:workspace/watch") + core.AssertContains(t, c.Commands(), "workspace/list") + core.AssertContains(t, c.Commands(), "agentic:workspace/list") + core.AssertContains(t, c.Commands(), "workspace/clean") + core.AssertContains(t, c.Commands(), "agentic:workspace/clean") + core.AssertContains(t, c.Commands(), "workspace/dispatch") + core.AssertContains(t, c.Commands(), "agentic:workspace/dispatch") + core.AssertContains(t, c.Commands(), "workspace/watch") + core.AssertContains(t, c.Commands(), "agentic:workspace/watch") } // --- CmdWorkspaceList Bad/Ugly --- @@ -40,7 +39,7 @@ func TestCommandsworkspace_CmdWorkspaceList_Bad_NoWorkspaceRootDir(t *testing.T) } r := s.cmdWorkspaceList(core.NewOptions()) - assert.True(t, r.OK) // gracefully says "no workspaces" + core.AssertTrue(t, r.OK) // gracefully says "no workspaces" } func TestCommandsworkspace_CmdWorkspaceList_Ugly_NonDirAndCorruptStatus(t *testing.T) { @@ -70,7 +69,7 @@ func TestCommandsworkspace_CmdWorkspaceList_Ugly_NonDirAndCorruptStatus(t *testi } r := s.cmdWorkspaceList(core.NewOptions()) - assert.True(t, r.OK) // should skip non-dir entries and still list valid workspaces + core.AssertTrue(t, r.OK) // should skip non-dir entries and still list valid workspaces } // --- CmdWorkspaceClean Bad/Ugly --- @@ -100,14 +99,14 @@ func TestCommandsworkspace_CmdWorkspaceClean_Bad_UnknownFilterLeavesEverything(t // Unknown filters now fail explicitly so agent callers can correct typos. r := s.cmdWorkspaceClean(core.NewOptions(core.Option{Key: "_arg", Value: "unknown"})) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) err, ok := r.Value.(error) - assert.True(t, ok) - assert.Contains(t, err.Error(), "unknown filter: unknown") + core.AssertTrue(t, ok) + core.AssertContains(t, err.Error(), "unknown filter: unknown") // All workspaces should still exist for _, name := range []string{"ws-done", "ws-fail", "ws-run"} { - assert.True(t, fs.IsDir(core.JoinPath(wsRoot, name)), "workspace %s should still exist", name) + core.AssertTrue(t, fs.IsDir(core.JoinPath(wsRoot, name)), "workspace %s should still exist", name) } } @@ -138,15 +137,15 @@ func TestCommandsworkspace_CmdWorkspaceClean_Ugly_MixedStatuses(t *testing.T) { // "all" filter removes completed, failed, blocked, merged, ready-for-review but NOT running/queued r := s.cmdWorkspaceClean(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) // merged, ready-for-review, blocked should be removed for _, name := range []string{"ws-merged", "ws-review", "ws-blocked"} { - assert.False(t, fs.Exists(core.JoinPath(wsRoot, name)), "workspace %s should be removed", name) + core.AssertFalse(t, fs.Exists(core.JoinPath(wsRoot, name)), "workspace %s should be removed", name) } // running and queued should remain for _, name := range []string{"ws-running", "ws-queued"} { - assert.True(t, fs.IsDir(core.JoinPath(wsRoot, name)), "workspace %s should still exist", name) + core.AssertTrue(t, fs.IsDir(core.JoinPath(wsRoot, name)), "workspace %s should still exist", name) } } @@ -185,10 +184,10 @@ func TestCommandsworkspace_CmdWorkspaceClean_Good_CapturesStatsBeforeDelete(t *t t.Cleanup(s.closeWorkspaceStatsStore) r := s.cmdWorkspaceClean(core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) // Workspace directory is gone. - assert.False(t, fs.Exists(workspaceDir)) + core.AssertFalse(t, fs.Exists(workspaceDir)) // Stats row survives in `.core/workspace/db.duckdb`. statsStore := s.workspaceStatsInstance() @@ -197,9 +196,9 @@ func TestCommandsworkspace_CmdWorkspaceClean_Good_CapturesStatsBeforeDelete(t *t } value, err := statsStore.Get(stateWorkspaceStatsGroup, "core/go-io/task-stats") - assert.NoError(t, err) - assert.Contains(t, value, "core/go-io/task-stats") - assert.Contains(t, value, "\"build_passed\":true") + core.AssertNoError(t, err) + core.AssertContains(t, value, "core/go-io/task-stats") + core.AssertContains(t, value, "\"build_passed\":true") } // --- CmdWorkspaceDispatch Ugly --- @@ -225,7 +224,7 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Ugly_AllFieldsSet(t *testing.T) )) // Dispatch calls the real method — fails because no source repo exists to clone. // The test verifies the CLI correctly passes all fields through to dispatch. - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestCommandsworkspace_CmdWorkspaceWatch_Good_ExplicitWorkspaceCompletes(t *testing.T) { @@ -251,13 +250,13 @@ func TestCommandsworkspace_CmdWorkspaceWatch_Good_ExplicitWorkspaceCompletes(t * core.Option{Key: "poll_interval", Value: 1}, core.Option{Key: "timeout", Value: 2}, )) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) output, ok := r.Value.(WatchOutput) - assert.True(t, ok) - assert.True(t, output.Success) - assert.Len(t, output.Completed, 1) - assert.Equal(t, "core/go-io/task-42", output.Completed[0].Workspace) + core.AssertTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertLen(t, output.Completed, 1) + core.AssertEqual(t, "core/go-io/task-42", output.Completed[0].Workspace) } func TestCommandsworkspace_WorkspaceDispatchInputFromOptions_Good_MapsFullContract(t *testing.T) { @@ -277,17 +276,17 @@ func TestCommandsworkspace_WorkspaceDispatchInputFromOptions_Good_MapsFullContra core.Option{Key: "dry_run", Value: true}, )) - assert.Equal(t, "go-io", input.Repo) - assert.Equal(t, "ship the release", input.Task) - assert.Equal(t, "codex:gpt-5.4", input.Agent) - assert.Equal(t, "core", input.Org) - assert.Equal(t, "coding", input.Template) - assert.Equal(t, "bug-fix", input.PlanTemplate) - assert.Equal(t, map[string]string{"ISSUE": "42", "MODE": "deep"}, input.Variables) - assert.Equal(t, "code/reviewer", input.Persona) - assert.Equal(t, 42, input.Issue) - assert.Equal(t, 7, input.PR) - assert.Equal(t, "feature/release", input.Branch) - assert.Equal(t, "v0.8.0", input.Tag) - assert.True(t, input.DryRun) + core.AssertEqual(t, "go-io", input.Repo) + core.AssertEqual(t, "ship the release", input.Task) + core.AssertEqual(t, "codex:gpt-5.4", input.Agent) + core.AssertEqual(t, "core", input.Org) + core.AssertEqual(t, "coding", input.Template) + core.AssertEqual(t, "bug-fix", input.PlanTemplate) + core.AssertEqual(t, map[string]string{"ISSUE": "42", "MODE": "deep"}, input.Variables) + core.AssertEqual(t, "code/reviewer", input.Persona) + core.AssertEqual(t, 42, input.Issue) + core.AssertEqual(t, 7, input.PR) + core.AssertEqual(t, "feature/release", input.Branch) + core.AssertEqual(t, "v0.8.0", input.Tag) + core.AssertTrue(t, input.DryRun) } diff --git a/pkg/agentic/commit.go b/pkg/agentic/commit.go index 1f3bc882..2e78f7fe 100644 --- a/pkg/agentic/commit.go +++ b/pkg/agentic/commit.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -122,7 +122,8 @@ func commitWorkspaceStatus(workspaceDir string) (*WorkspaceStatus, error) { } func commitEnsureMetaDir(metaDir string) error { - if r := fs.EnsureDir(metaDir); r.OK { + r := fs.EnsureDir(metaDir) + if r.OK { return nil } err, _ := r.Value.(error) @@ -174,7 +175,8 @@ func commitWriteMarker(markerPath, workspaceDir string, workspaceStatus *Workspa CommittedAt: committedAt, } - if r := fs.WriteAtomic(markerPath, core.JSONMarshalString(marker)); r.OK { + r := fs.WriteAtomic(markerPath, core.JSONMarshalString(marker)) + if r.OK { return nil } diff --git a/pkg/agentic/commit_test.go b/pkg/agentic/commit_test.go index 99ed0bb9..6da10b9f 100644 --- a/pkg/agentic/commit_test.go +++ b/pkg/agentic/commit_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestCommit_HandleCommit_Good_WritesJournal(t *testing.T) { @@ -18,8 +16,8 @@ func TestCommit_HandleCommit_Good_WritesJournal(t *testing.T) { workspaceName := "core/go-io/task-42" workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) metaDir := WorkspaceMetaDir(workspaceDir) - require.True(t, fs.EnsureDir(metaDir).OK) - require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(metaDir).OK) + core.RequireTrue(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "merged", Agent: "codex", Repo: "go-io", @@ -28,36 +26,36 @@ func TestCommit_HandleCommit_Good_WritesJournal(t *testing.T) { Branch: "agent/fix-tests", Runs: 3, }) == nil) - require.True(t, fs.Write(core.JoinPath(metaDir, "report.json"), `{"findings":[{"file":"main.go"}],"changes":{"files_changed":1}}`).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(metaDir, "report.json"), `{"findings":[{"file":"main.go"}],"changes":{"files_changed":1}}`).OK) s := &PrepSubsystem{} result := s.handleCommit(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: workspaceName}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(CommitOutput) - require.True(t, ok) - assert.Equal(t, workspaceName, output.Workspace) - assert.False(t, output.Skipped) - assert.NotEmpty(t, output.CommittedAt) + core.RequireTrue(t, ok) + core.AssertEqual(t, workspaceName, output.Workspace) + core.AssertFalse(t, output.Skipped) + core.AssertNotEmpty(t, output.CommittedAt) journal := fs.Read(output.JournalPath) - require.True(t, journal.OK) - assert.Contains(t, journal.Value.(string), `"repo":"go-io"`) - assert.Contains(t, journal.Value.(string), `"committed_at"`) + core.RequireTrue(t, journal.OK) + core.AssertContains(t, journal.Value.(string), `"repo":"go-io"`) + core.AssertContains(t, journal.Value.(string), `"committed_at"`) marker := fs.Read(output.MarkerPath) - require.True(t, marker.OK) - assert.Contains(t, marker.Value.(string), `"workspace":"core/go-io/task-42"`) + core.RequireTrue(t, marker.OK) + core.AssertContains(t, marker.Value.(string), `"workspace":"core/go-io/task-42"`) } func TestCommit_HandleCommit_Bad_MissingWorkspace(t *testing.T) { s := &PrepSubsystem{} result := s.handleCommit(context.Background(), core.NewOptions()) - assert.False(t, result.OK) - assert.Error(t, result.Value.(error)) + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) } func TestCommit_HandleCommit_Ugly_Idempotent(t *testing.T) { @@ -66,8 +64,8 @@ func TestCommit_HandleCommit_Ugly_Idempotent(t *testing.T) { workspaceName := "core/go-io/task-43" workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) - require.True(t, fs.EnsureDir(WorkspaceMetaDir(workspaceDir)).OK) - require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(WorkspaceMetaDir(workspaceDir)).OK) + core.RequireTrue(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "completed", Agent: "codex", Repo: "go-io", @@ -81,21 +79,21 @@ func TestCommit_HandleCommit_Ugly_Idempotent(t *testing.T) { first := s.handleCommit(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: workspaceName}, )) - require.True(t, first.OK) + core.RequireTrue(t, first.OK) second := s.handleCommit(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: workspaceName}, )) - require.True(t, second.OK) + core.RequireTrue(t, second.OK) output, ok := second.Value.(CommitOutput) - require.True(t, ok) - assert.True(t, output.Skipped) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Skipped) journal := fs.Read(output.JournalPath) - require.True(t, journal.OK) + core.RequireTrue(t, journal.OK) lines := len(core.Split(core.Trim(journal.Value.(string)), "\n")) - assert.Equal(t, 1, lines) + core.AssertEqual(t, 1, lines) } func TestCommit_HandleCommit_Ugly_CorruptMarkerIsPreserved(t *testing.T) { @@ -105,8 +103,8 @@ func TestCommit_HandleCommit_Ugly_CorruptMarkerIsPreserved(t *testing.T) { workspaceName := "core/go-io/task-44" workspaceDir := core.JoinPath(WorkspaceRoot(), workspaceName) metaDir := WorkspaceMetaDir(workspaceDir) - require.True(t, fs.EnsureDir(metaDir).OK) - require.True(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(metaDir).OK) + core.RequireTrue(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "completed", Agent: "codex", Repo: "go-io", @@ -115,21 +113,21 @@ func TestCommit_HandleCommit_Ugly_CorruptMarkerIsPreserved(t *testing.T) { Branch: "agent/fix-tests", Runs: 2, }) == nil) - require.True(t, fs.Write(core.JoinPath(metaDir, "commit.json"), "{not-json").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(metaDir, "commit.json"), "{not-json").OK) s := &PrepSubsystem{} result := s.handleCommit(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: workspaceName}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(CommitOutput) - require.True(t, ok) - assert.False(t, output.Skipped) + core.RequireTrue(t, ok) + core.AssertFalse(t, output.Skipped) marker := fs.Read(output.MarkerPath) - require.True(t, marker.OK) - assert.Contains(t, marker.Value.(string), `"workspace":"core/go-io/task-44"`) + core.RequireTrue(t, marker.OK) + core.AssertContains(t, marker.Value.(string), `"workspace":"core/go-io/task-44"`) entries := listDirNames(fs.List(metaDir)) var backupPath string @@ -139,9 +137,9 @@ func TestCommit_HandleCommit_Ugly_CorruptMarkerIsPreserved(t *testing.T) { break } } - require.NotEmpty(t, backupPath) + core.RequireNotEmpty(t, backupPath) backup := fs.Read(backupPath) - require.True(t, backup.OK) - assert.Equal(t, "{not-json", backup.Value.(string)) + core.RequireTrue(t, backup.OK) + core.AssertEqual(t, "{not-json", backup.Value.(string)) } diff --git a/pkg/agentic/content.go b/pkg/agentic/content.go index 3147646d..4161f044 100644 --- a/pkg/agentic/content.go +++ b/pkg/agentic/content.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/content_seo.go b/pkg/agentic/content_seo.go index 40c2221e..b0e08d26 100644 --- a/pkg/agentic/content_seo.go +++ b/pkg/agentic/content_seo.go @@ -10,7 +10,7 @@ import ( "net/http" "time" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" store "dappco.re/go/store" "github.com/gin-gonic/gin" diff --git a/pkg/agentic/content_seo_test.go b/pkg/agentic/content_seo_test.go index 3833442e..91d5e464 100644 --- a/pkg/agentic/content_seo_test.go +++ b/pkg/agentic/content_seo_test.go @@ -4,16 +4,18 @@ package agentic import ( "context" + "net/http" + "net/http/httptest" "testing" "time" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" + "github.com/gin-gonic/gin" mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestScheduleRevision_Good_CreatesPendingRevision(t *testing.T) { +func TestPendingRevision_PrepSubsystem_ScheduleRevision_Good(t *testing.T) { withStateStoreTempDir(t) now := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) @@ -23,38 +25,38 @@ func TestScheduleRevision_Good_CreatesPendingRevision(t *testing.T) { defer subsystem.closeStateStore() revision, err := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Updated copy") - require.NoError(t, err) - assert.Equal(t, "/help/hosting", revision.PageID) - assert.Equal(t, "Updated copy", revision.Content) - assert.Nil(t, revision.ScheduledAt) - assert.True(t, revision.CreatedAt.Equal(now)) + core.RequireNoError(t, err) + core.AssertEqual(t, "/help/hosting", revision.PageID) + core.AssertEqual(t, "Updated copy", revision.Content) + core.AssertNil(t, revision.ScheduledAt) + core.AssertTrue(t, revision.CreatedAt.Equal(now)) pending, err := subsystem.GetPendingRevisions("/help/hosting") - require.NoError(t, err) - require.Len(t, pending, 1) - assert.Nil(t, pending[0].ScheduledAt) + core.RequireNoError(t, err) + core.AssertLen(t, pending, 1) + core.AssertNil(t, pending[0].ScheduledAt) var rawEntries []string subsystem.stateStoreRestore(contentSEORevisionGroup, func(_ string, value string) bool { rawEntries = append(rawEntries, value) return true }) - require.Len(t, rawEntries, 1) - assert.Contains(t, rawEntries[0], `"scheduled_at":null`) + core.AssertLen(t, rawEntries, 1) + core.AssertContains(t, rawEntries[0], `"scheduled_at":null`) } -func TestScheduleRevision_Bad_EmptyPageID(t *testing.T) { +func TestEmptyPage_PrepSubsystem_ScheduleRevision_Bad(t *testing.T) { withStateStoreTempDir(t) subsystem := &PrepSubsystem{} defer subsystem.closeStateStore() _, err := subsystem.ScheduleRevision(context.Background(), "", "Updated copy") - require.Error(t, err) - assert.Contains(t, err.Error(), "page_id is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "page_id is required") } -func TestOnGooglebotVisit_Good_SetsPublishTimeInRange(t *testing.T) { +func TestPublishWindow_PrepSubsystem_OnGooglebotVisit_Good(t *testing.T) { withStateStoreTempDir(t) now := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) @@ -65,54 +67,140 @@ func TestOnGooglebotVisit_Good_SetsPublishTimeInRange(t *testing.T) { defer subsystem.closeStateStore() _, err := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Updated copy") - require.NoError(t, err) - require.NoError(t, subsystem.OnGooglebotVisit(context.Background(), "/help/hosting")) + core.RequireNoError(t, err) + core.RequireNoError(t, subsystem.OnGooglebotVisit(context.Background(), "/help/hosting")) pending, err := subsystem.GetPendingRevisions("/help/hosting") - require.NoError(t, err) - assert.Len(t, pending, 0) + core.RequireNoError(t, err) + core.AssertLen(t, pending, 0) records, err := subsystem.contentSEORevisionRecords(subsystem.stateStoreInstance(), "/help/hosting", false) - require.NoError(t, err) - require.Len(t, records, 1) - require.NotNil(t, records[0].Revision.ScheduledAt) + core.RequireNoError(t, err) + core.AssertLen(t, records, 1) + core.AssertNotNil(t, records[0].Revision.ScheduledAt) delta := records[0].Revision.ScheduledAt.Sub(now) - assert.GreaterOrEqual(t, delta, 8*time.Minute) - assert.LessOrEqual(t, delta, 62*time.Minute) - assert.Equal(t, 37*time.Minute, delta) + core.AssertGreaterOrEqual(t, delta, 8*time.Minute) + core.AssertLessOrEqual(t, delta, 62*time.Minute) + core.AssertEqual(t, 37*time.Minute, delta) } -func TestOnGooglebotVisit_Bad_NoPendingRevision(t *testing.T) { +func TestNoPendingRevision_PrepSubsystem_OnGooglebotVisit_Bad(t *testing.T) { withStateStoreTempDir(t) subsystem := &PrepSubsystem{} defer subsystem.closeStateStore() - require.NoError(t, subsystem.OnGooglebotVisit(context.Background(), "/help/hosting")) - assert.Equal(t, 0, subsystem.stateStoreCount(contentSEORevisionGroup)) + core.RequireNoError(t, subsystem.OnGooglebotVisit(context.Background(), "/help/hosting")) + core.AssertEqual(t, 0, subsystem.stateStoreCount(contentSEORevisionGroup)) } -func TestOnGooglebotVisit_Ugly_StoreError(t *testing.T) { +func TestStoreError_PrepSubsystem_OnGooglebotVisit_Ugly(t *testing.T) { root := t.TempDir() blocked := root + "/blocked" writeResult := fs.Write(blocked, "blocked") - require.True(t, writeResult.OK) + core.RequireTrue(t, writeResult.OK) t.Setenv("CORE_WORKSPACE", blocked) subsystem := &PrepSubsystem{} defer subsystem.closeStateStore() err := subsystem.OnGooglebotVisit(context.Background(), "/help/hosting") - require.Error(t, err) - assert.Contains(t, err.Error(), "state store unavailable") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "state store unavailable") +} + +func TestPendingList_PrepSubsystem_GetPendingRevisions_Good(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + _, err := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Updated copy") + core.RequireNoError(t, err) + + pending, err := subsystem.GetPendingRevisions("/help/hosting") + core.RequireNoError(t, err) + core.AssertLen(t, pending, 1) + core.AssertEqual(t, "/help/hosting", pending[0].PageID) +} + +func TestEmptyPage_PrepSubsystem_GetPendingRevisions_Bad(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + pending, err := subsystem.GetPendingRevisions("") + core.AssertError(t, err) + core.AssertNil(t, pending) +} + +func TestScheduledFiltered_PrepSubsystem_GetPendingRevisions_Ugly(t *testing.T) { + withStateStoreTempDir(t) + + now := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) + restoreContentSEONow(t, now) + restoreContentSEORandomDelay(t, 37*time.Minute) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + _, err := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Updated copy") + core.RequireNoError(t, err) + core.RequireNoError(t, subsystem.OnGooglebotVisit(context.Background(), "/help/hosting")) + + pending, err := subsystem.GetPendingRevisions("/help/hosting") + core.RequireNoError(t, err) + core.AssertLen(t, pending, 0) +} + +func TestGooglebot_PrepSubsystem_HandleGooglebotVisit_Good(t *testing.T) { + withStateStoreTempDir(t) + + now := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) + restoreContentSEONow(t, now) + restoreContentSEORandomDelay(t, 37*time.Minute) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + _, err := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Updated copy") + core.RequireNoError(t, err) + core.RequireNoError(t, subsystem.HandleGooglebotVisit(context.Background(), "/help/hosting", "Googlebot/2.1")) + + pending, err := subsystem.GetPendingRevisions("/help/hosting") + core.RequireNoError(t, err) + core.AssertLen(t, pending, 0) +} + +func TestNonGooglebot_PrepSubsystem_HandleGooglebotVisit_Bad(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + err := subsystem.HandleGooglebotVisit(context.Background(), "/help/hosting", "Mozilla/5.0") + core.RequireNoError(t, err) + core.AssertEqual(t, 0, subsystem.stateStoreCount(contentSEORevisionGroup)) +} + +func TestEmptyPage_PrepSubsystem_HandleGooglebotVisit_Ugly(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + err := subsystem.HandleGooglebotVisit(context.Background(), "", "Googlebot/2.1") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "page_id is required") } func TestContentSEO_RegisterTools_Good_RegistersScheduleTool(t *testing.T) { t.Setenv("CORE_MCP_FULL", "1") svc, err := coremcp.New(coremcp.Options{Unrestricted: true}) - require.NoError(t, err) + core.RequireNoError(t, err) subsystem := &PrepSubsystem{} subsystem.RegisterTools(svc) @@ -122,22 +210,89 @@ func TestContentSEO_RegisterTools_Good_RegistersScheduleTool(t *testing.T) { clientTransport, serverTransport := mcpsdk.NewInMemoryTransports() serverSession, err := server.Connect(context.Background(), serverTransport, nil) - require.NoError(t, err) + core.RequireNoError(t, err) t.Cleanup(func() { _ = serverSession.Close() }) clientSession, err := client.Connect(context.Background(), clientTransport, nil) - require.NoError(t, err) + core.RequireNoError(t, err) t.Cleanup(func() { _ = clientSession.Close() }) result, err := clientSession.ListTools(context.Background(), nil) - require.NoError(t, err) + core.RequireNoError(t, err) var toolNames []string for _, tool := range result.Tools { toolNames = append(toolNames, tool.Name) } - assert.Contains(t, toolNames, "content_seo_schedule") + core.AssertContains(t, toolNames, "content_seo_schedule") +} + +func TestGooglebotMiddleware_PrepSubsystem_ContentSEOGooglebotMiddleware_Good(t *testing.T) { + withStateStoreTempDir(t) + + now := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) + restoreContentSEONow(t, now) + restoreContentSEORandomDelay(t, 37*time.Minute) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + _, err := subsystem.ScheduleRevision(context.Background(), "/help/hosting", "Updated copy") + core.RequireNoError(t, err) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/help/hosting", nil) + c.Request.Header.Set("User-Agent", "Googlebot/2.1") + + subsystem.ContentSEOGooglebotMiddleware(nil)(c) + + pending, err := subsystem.GetPendingRevisions("/help/hosting") + core.RequireNoError(t, err) + core.AssertLen(t, pending, 0) +} + +func TestNonGooglebotMiddleware_PrepSubsystem_ContentSEOGooglebotMiddleware_Bad(t *testing.T) { + withStateStoreTempDir(t) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/help/hosting", nil) + c.Request.Header.Set("User-Agent", "Mozilla/5.0") + + core.AssertNotPanics(t, func() { + subsystem.ContentSEOGooglebotMiddleware(nil)(c) + }) + core.AssertEqual(t, 0, subsystem.stateStoreCount(contentSEORevisionGroup)) +} + +func TestResolvedPage_PrepSubsystem_ContentSEOGooglebotMiddleware_Ugly(t *testing.T) { + withStateStoreTempDir(t) + + now := time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC) + restoreContentSEONow(t, now) + restoreContentSEORandomDelay(t, 37*time.Minute) + + subsystem := &PrepSubsystem{} + defer subsystem.closeStateStore() + + _, err := subsystem.ScheduleRevision(context.Background(), "/resolved/page", "Updated copy") + core.RequireNoError(t, err) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/ignored", nil) + c.Request.Header.Set("User-Agent", "Googlebot/2.1") + + subsystem.ContentSEOGooglebotMiddleware(func(*gin.Context) string { return "/resolved/page" })(c) + + pending, err := subsystem.GetPendingRevisions("/resolved/page") + core.RequireNoError(t, err) + core.AssertLen(t, pending, 0) } func restoreContentSEONow(t *testing.T, now time.Time) { diff --git a/pkg/agentic/content_test.go b/pkg/agentic/content_test.go index 320f5da6..f45cd34d 100644 --- a/pkg/agentic/content_test.go +++ b/pkg/agentic/content_test.go @@ -8,29 +8,27 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestContent_HandleContentGenerate_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/generate", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) - require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/content/generate", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "Bearer secret-token", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "Draft a release note", payload["prompt"]) - require.Equal(t, "claude", payload["provider"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "Draft a release note", payload["prompt"]) + core.AssertEqual(t, "claude", payload["provider"]) config, ok := payload["config"].(map[string]any) - require.True(t, ok) - require.Equal(t, float64(4000), config["max_tokens"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, float64(4000), config["max_tokens"]) _, _ = w.Write([]byte(`{"data":{"id":"gen_1","provider":"claude","model":"claude-3.7-sonnet","content":"Release notes draft","input_tokens":12,"output_tokens":48,"duration_ms":321}}`)) })) @@ -42,31 +40,31 @@ func TestContent_HandleContentGenerate_Good(t *testing.T) { core.Option{Key: "provider", Value: "claude"}, core.Option{Key: "config", Value: `{"max_tokens":4000}`}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentGenerateOutput) - require.True(t, ok) - assert.Equal(t, "gen_1", output.Result.ID) - assert.Equal(t, "claude", output.Result.Provider) - assert.Equal(t, "claude-3.7-sonnet", output.Result.Model) - assert.Equal(t, "Release notes draft", output.Result.Content) - assert.Equal(t, 48, output.Result.OutputTokens) + core.RequireTrue(t, ok) + core.AssertEqual(t, "gen_1", output.Result.ID) + core.AssertEqual(t, "claude", output.Result.Provider) + core.AssertEqual(t, "claude-3.7-sonnet", output.Result.Model) + core.AssertEqual(t, "Release notes draft", output.Result.Content) + core.AssertEqual(t, 48, output.Result.OutputTokens) } func TestContent_HandleContentGenerate_Good_BriefTemplate(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/generate", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/content/generate", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "brief_1", payload["brief_id"]) - require.Equal(t, "help-article", payload["template"]) - require.NotContains(t, payload, "prompt") + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "brief_1", payload["brief_id"]) + core.AssertEqual(t, "help-article", payload["template"]) + core.AssertNotContains(t, payload, "prompt") _, _ = w.Write([]byte(`{"data":{"result":{"id":"gen_2","provider":"claude","model":"claude-3.7-sonnet","content":"Template draft","status":"completed"}}}`)) })) @@ -78,19 +76,19 @@ func TestContent_HandleContentGenerate_Good_BriefTemplate(t *testing.T) { core.Option{Key: "template", Value: "help-article"}, core.Option{Key: "provider", Value: "claude"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentGenerateOutput) - require.True(t, ok) - assert.Equal(t, "gen_2", output.Result.ID) - assert.Equal(t, "Template draft", output.Result.Content) + core.RequireTrue(t, ok) + core.AssertEqual(t, "gen_2", output.Result.ID) + core.AssertEqual(t, "Template draft", output.Result.Content) } func TestContent_HandleContentGenerate_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "secret-token") result := subsystem.handleContentGenerate(context.Background(), core.NewOptions()) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestContent_HandleContentGenerate_Ugly(t *testing.T) { @@ -103,22 +101,22 @@ func TestContent_HandleContentGenerate_Ugly(t *testing.T) { result := subsystem.handleContentGenerate(context.Background(), core.NewOptions( core.Option{Key: "prompt", Value: "Draft a release note"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestContent_HandleContentBriefCreate_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/briefs", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/content/briefs", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "LinkHost brief", payload["title"]) - require.Equal(t, "LinkHost", payload["product"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "LinkHost brief", payload["title"]) + core.AssertEqual(t, "LinkHost", payload["product"]) _, _ = w.Write([]byte(`{"data":{"brief":{"id":"brief_1","slug":"host-link","title":"LinkHost brief","product":"LinkHost","category":"product","brief":"Core context"}}}`)) })) @@ -131,20 +129,20 @@ func TestContent_HandleContentBriefCreate_Good(t *testing.T) { core.Option{Key: "category", Value: "product"}, core.Option{Key: "brief", Value: "Core context"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentBriefOutput) - require.True(t, ok) - assert.Equal(t, "brief_1", output.Brief.ID) - assert.Equal(t, "host-link", output.Brief.Slug) - assert.Equal(t, "LinkHost", output.Brief.Product) + core.RequireTrue(t, ok) + core.AssertEqual(t, "brief_1", output.Brief.ID) + core.AssertEqual(t, "host-link", output.Brief.Slug) + core.AssertEqual(t, "LinkHost", output.Brief.Product) } func TestContent_HandleContentBriefList_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/briefs", r.URL.Path) - require.Equal(t, "product", r.URL.Query().Get("category")) - require.Equal(t, "5", r.URL.Query().Get("limit")) + core.AssertEqual(t, "/v1/content/briefs", r.URL.Path) + core.AssertEqual(t, "product", r.URL.Query().Get("category")) + core.AssertEqual(t, "5", r.URL.Query().Get("limit")) _, _ = w.Write([]byte(`{"data":{"briefs":[{"id":"brief_1","slug":"host-link","title":"LinkHost brief","category":"product"}],"total":1}}`)) })) defer server.Close() @@ -154,18 +152,18 @@ func TestContent_HandleContentBriefList_Good(t *testing.T) { core.Option{Key: "category", Value: "product"}, core.Option{Key: "limit", Value: 5}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentBriefListOutput) - require.True(t, ok) - assert.Equal(t, 1, output.Total) - require.Len(t, output.Briefs, 1) - assert.Equal(t, "host-link", output.Briefs[0].Slug) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, output.Total) + core.AssertLen(t, output.Briefs, 1) + core.AssertEqual(t, "host-link", output.Briefs[0].Slug) } func TestContent_HandleContentStatus_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/status/batch_123", r.URL.Path) + core.AssertEqual(t, "/v1/content/status/batch_123", r.URL.Path) _, _ = w.Write([]byte(`{"data":{"status":"running","batch_id":"batch_123","queued":2}}`)) })) defer server.Close() @@ -174,19 +172,19 @@ func TestContent_HandleContentStatus_Good(t *testing.T) { result := subsystem.handleContentStatus(context.Background(), core.NewOptions( core.Option{Key: "batch_id", Value: "batch_123"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentStatusOutput) - require.True(t, ok) - assert.Equal(t, "running", stringValue(output.Status["status"])) - assert.Equal(t, "batch_123", stringValue(output.Status["batch_id"])) + core.RequireTrue(t, ok) + core.AssertEqual(t, "running", stringValue(output.Status["status"])) + core.AssertEqual(t, "batch_123", stringValue(output.Status["batch_id"])) } func TestContent_HandleContentUsageStats_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/usage/stats", r.URL.Path) - require.Equal(t, "claude", r.URL.Query().Get("provider")) - require.Equal(t, "week", r.URL.Query().Get("period")) + core.AssertEqual(t, "/v1/content/usage/stats", r.URL.Path) + core.AssertEqual(t, "claude", r.URL.Query().Get("provider")) + core.AssertEqual(t, "week", r.URL.Query().Get("period")) _, _ = w.Write([]byte(`{"data":{"calls":4,"tokens":1200}}`)) })) defer server.Close() @@ -196,27 +194,27 @@ func TestContent_HandleContentUsageStats_Good(t *testing.T) { core.Option{Key: "provider", Value: "claude"}, core.Option{Key: "period", Value: "week"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentUsageStatsOutput) - require.True(t, ok) - assert.Equal(t, 4, intValue(output.Usage["calls"])) - assert.Equal(t, 1200, intValue(output.Usage["tokens"])) + core.RequireTrue(t, ok) + core.AssertEqual(t, 4, intValue(output.Usage["calls"])) + core.AssertEqual(t, 1200, intValue(output.Usage["tokens"])) } func TestContent_HandleContentFromPlan_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/content/from-plan", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/content/from-plan", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "release-notes", payload["plan_slug"]) - require.Equal(t, "openai", payload["provider"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "release-notes", payload["plan_slug"]) + core.AssertEqual(t, "openai", payload["provider"]) _, _ = w.Write([]byte(`{"data":{"result":{"batch_id":"batch_123","provider":"openai","model":"gpt-5.4","content":"Plan-driven draft","status":"completed"}}}`)) })) @@ -227,13 +225,13 @@ func TestContent_HandleContentFromPlan_Good(t *testing.T) { core.Option{Key: "plan_slug", Value: "release-notes"}, core.Option{Key: "provider", Value: "openai"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentFromPlanOutput) - require.True(t, ok) - assert.Equal(t, "batch_123", output.Result.BatchID) - assert.Equal(t, "completed", output.Result.Status) - assert.Equal(t, "Plan-driven draft", output.Result.Content) + core.RequireTrue(t, ok) + core.AssertEqual(t, "batch_123", output.Result.BatchID) + core.AssertEqual(t, "completed", output.Result.Status) + core.AssertEqual(t, "Plan-driven draft", output.Result.Content) } func TestContent_HandleContentSchemaGenerate_Good_Article(t *testing.T) { @@ -246,20 +244,20 @@ func TestContent_HandleContentSchemaGenerate_Good_Article(t *testing.T) { core.Option{Key: "author", Value: "Virgil"}, core.Option{Key: "language", Value: "en-GB"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentSchemaOutput) - require.True(t, ok) - assert.Equal(t, "BlogPosting", output.SchemaType) - assert.Equal(t, "Release notes", output.Schema["headline"]) - assert.Equal(t, "What changed in this release", output.Schema["description"]) - assert.Equal(t, "https://example.test/releases/1", output.Schema["url"]) - assert.Equal(t, "en-GB", output.Schema["inLanguage"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, "BlogPosting", output.SchemaType) + core.AssertEqual(t, "Release notes", output.Schema["headline"]) + core.AssertEqual(t, "What changed in this release", output.Schema["description"]) + core.AssertEqual(t, "https://example.test/releases/1", output.Schema["url"]) + core.AssertEqual(t, "en-GB", output.Schema["inLanguage"]) author, ok := output.Schema["author"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "Virgil", author["name"]) - assert.Contains(t, output.SchemaJSON, `"@type":"BlogPosting"`) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Virgil", author["name"]) + core.AssertContains(t, output.SchemaJSON, `"@type":"BlogPosting"`) } func TestContent_HandleContentSchemaGenerate_Good_FAQ(t *testing.T) { @@ -269,21 +267,21 @@ func TestContent_HandleContentSchemaGenerate_Good_FAQ(t *testing.T) { core.Option{Key: "title", Value: "Release notes FAQ"}, core.Option{Key: "questions", Value: `[{"question":"What changed?","answer":"We added schema generation."}]`}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentSchemaOutput) - require.True(t, ok) - assert.Equal(t, "FAQPage", output.SchemaType) + core.RequireTrue(t, ok) + core.AssertEqual(t, "FAQPage", output.SchemaType) entries, ok := output.Schema["mainEntity"].([]map[string]any) - require.True(t, ok) - require.Len(t, entries, 1) - assert.Equal(t, "Question", entries[0]["@type"]) - assert.Equal(t, "What changed?", entries[0]["name"]) + core.RequireTrue(t, ok) + core.AssertLen(t, entries, 1) + core.AssertEqual(t, "Question", entries[0]["@type"]) + core.AssertEqual(t, "What changed?", entries[0]["name"]) answer, ok := entries[0]["acceptedAnswer"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "Answer", answer["@type"]) - assert.Equal(t, "We added schema generation.", answer["text"]) + core.RequireTrue(t, ok) + core.AssertEqual(t, "Answer", answer["@type"]) + core.AssertEqual(t, "We added schema generation.", answer["text"]) } func TestContent_HandleContentSchemaGenerate_Good_HowTo(t *testing.T) { @@ -293,18 +291,18 @@ func TestContent_HandleContentSchemaGenerate_Good_HowTo(t *testing.T) { core.Option{Key: "title", Value: "Set up the workspace"}, core.Option{Key: "steps", Value: `[{"name":"Prepare","text":"Clone the repo"},{"name":"Run","text":"Start the agent"}]`}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(ContentSchemaOutput) - require.True(t, ok) - assert.Equal(t, "HowTo", output.SchemaType) + core.RequireTrue(t, ok) + core.AssertEqual(t, "HowTo", output.SchemaType) steps, ok := output.Schema["step"].([]map[string]any) - require.True(t, ok) - require.Len(t, steps, 2) - assert.Equal(t, "HowToStep", steps[0]["@type"]) - assert.Equal(t, "Prepare", steps[0]["name"]) - assert.Equal(t, "Clone the repo", steps[0]["text"]) + core.RequireTrue(t, ok) + core.AssertLen(t, steps, 2) + core.AssertEqual(t, "HowToStep", steps[0]["@type"]) + core.AssertEqual(t, "Prepare", steps[0]["name"]) + core.AssertEqual(t, "Clone the repo", steps[0]["text"]) } func TestContent_HandleContentSchemaGenerate_Bad(t *testing.T) { @@ -312,7 +310,7 @@ func TestContent_HandleContentSchemaGenerate_Bad(t *testing.T) { result := subsystem.handleContentSchemaGenerate(context.Background(), core.NewOptions( core.Option{Key: "type", Value: "article"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestContent_HandleContentSchemaGenerate_Ugly(t *testing.T) { @@ -322,20 +320,24 @@ func TestContent_HandleContentSchemaGenerate_Ugly(t *testing.T) { core.Option{Key: "title", Value: "FAQ"}, core.Option{Key: "questions", Value: `[{"question":"What changed?"`}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } func TestContent_contentSchemaType_Good(t *testing.T) { - assert.Equal(t, "BlogPosting", contentSchemaType("article")) - assert.Equal(t, "FAQPage", contentSchemaType("faq")) - assert.Equal(t, "HowTo", contentSchemaType("how-to")) + core.AssertEqual(t, "BlogPosting", contentSchemaType("article")) + core.AssertEqual(t, "FAQPage", contentSchemaType("faq")) + core.AssertEqual(t, "HowTo", contentSchemaType("how-to")) } func TestContent_contentSchemaType_Bad(t *testing.T) { - assert.Empty(t, contentSchemaType("press-release")) + result := contentSchemaType("press-release") + core.AssertEmpty(t, result) + core.AssertEqual(t, "", result) } func TestContent_contentSchemaType_Ugly(t *testing.T) { - assert.Equal(t, "TechArticle", contentSchemaType(" TECH-ARTICLE ")) - assert.Empty(t, contentSchemaType("")) + techArticle := contentSchemaType(" TECH-ARTICLE ") + empty := contentSchemaType("") + core.AssertEqual(t, "TechArticle", techArticle) + core.AssertEmpty(t, empty) } diff --git a/pkg/agentic/deps.go b/pkg/agentic/deps.go index 742c6fbb..1da4e731 100644 --- a/pkg/agentic/deps.go +++ b/pkg/agentic/deps.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" ) // s.cloneWorkspaceDeps(ctx, workspaceDir, repoDir, "core") @@ -67,7 +67,7 @@ func (s *PrepSubsystem) cloneWorkspaceDeps(ctx context.Context, workspaceDir, re return nil } -// dep := coreDep{module: "dappco.re/go/core", repo: "go", dir: "core-go"} +// dep := coreDep{module: "dappco.re/go", repo: "go", dir: "core-go"} type coreDep struct { module string repo string @@ -87,30 +87,42 @@ func parseCoreDeps(gomod string) []coreDep { continue } - if core.HasPrefix(line, "dappco.re/go/") { - parts := core.Split(line, " ") - mod := parts[0] - if seen[mod] { - continue - } - seen[mod] = true - - suffix := core.TrimPrefix(mod, "dappco.re/go/") - repo := suffix - if core.HasPrefix(suffix, "core/") { - repoSuffix := core.TrimPrefix(suffix, "core/") - repo = core.Concat("go-", repoSuffix) - } else if suffix == "core" { - repo = "go" - } - dir := core.Concat("core-", core.Replace(repo, "/", "-")) - deps = append(deps, coreDep{module: mod, repo: repo, dir: dir}) + parts := core.Split(line, " ") + if len(parts) == 0 { + continue } + + mod := normaliseCoreDepModule(parts[0]) + if mod != "dappco.re/go" && !core.HasPrefix(mod, "dappco.re/go/") { + continue + } + if seen[mod] { + continue + } + seen[mod] = true + + suffix := core.TrimPrefix(core.TrimPrefix(mod, "dappco.re/go"), "/") + repo := coreDepRepo(suffix) + dir := core.Concat("core-", core.Replace(repo, "/", "-")) + deps = append(deps, coreDep{module: mod, repo: repo, dir: dir}) } return deps } +func normaliseCoreDepModule(mod string) string { return mod } + +func coreDepRepo(suffix string) string { + switch suffix { + case "": + return "go" + case "mcp": + return "mcp" + default: + return core.Concat("go-", suffix) + } +} + // url := forgeSSHURL("core", "go-io") // core.Println(url) // "ssh://git@forge.lthn.ai:2223/core/go-io.git" func forgeSSHURL(org, repo string) string { diff --git a/pkg/agentic/deps_example_test.go b/pkg/agentic/deps_example_test.go index 49a2f121..d6c0d9e7 100644 --- a/pkg/agentic/deps_example_test.go +++ b/pkg/agentic/deps_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func Example_forgeSSHURL() { core.Println(forgeSSHURL("core", "go-io")) @@ -11,8 +11,8 @@ func Example_forgeSSHURL() { func Example_parseCoreDeps() { goMod := `require ( - dappco.re/go/core v0.8.0 - dappco.re/go/core/process v0.3.0 + dappco.re/go v0.8.0 + dappco.re/go/process v0.3.0 )` core.Println(len(parseCoreDeps(goMod))) diff --git a/pkg/agentic/deps_test.go b/pkg/agentic/deps_test.go index 640ee2e9..914c8406 100644 --- a/pkg/agentic/deps_test.go +++ b/pkg/agentic/deps_test.go @@ -6,8 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" + core "dappco.re/go" ) func TestDeps_ParseCoreDeps_Good(t *testing.T) { @@ -16,15 +15,15 @@ func TestDeps_ParseCoreDeps_Good(t *testing.T) { go 1.26.0 require ( - dappco.re/go/core v0.8.0 - dappco.re/go/core/process v0.3.0 + dappco.re/go v0.8.0 + dappco.re/go/process v0.3.0 dappco.re/go/mcp v0.4.0 )` deps := parseCoreDeps(goMod) - assert.Equal(t, []coreDep{ - {module: "dappco.re/go/core", repo: "go", dir: "core-go"}, + core.AssertEqual(t, []coreDep{ + {module: "dappco.re/go", repo: "go", dir: "core-go"}, {module: "dappco.re/go/process", repo: "go-process", dir: "core-go-process"}, {module: "dappco.re/go/mcp", repo: "mcp", dir: "core-mcp"}, }, deps) @@ -37,7 +36,7 @@ go 1.26.0 require github.com/stretchr/testify v1.11.1` - assert.Empty(t, parseCoreDeps(goMod)) + core.AssertEmpty(t, parseCoreDeps(goMod)) } func TestDeps_ParseCoreDeps_Ugly_DeduplicatesAndSkipsIndirect(t *testing.T) { @@ -46,20 +45,22 @@ func TestDeps_ParseCoreDeps_Ugly_DeduplicatesAndSkipsIndirect(t *testing.T) { go 1.26.0 require ( - dappco.re/go/core v0.8.0 - dappco.re/go/core v0.8.0 - dappco.re/go/core/ws v0.2.0 // indirect - dappco.re/go/core/process v0.3.0 + dappco.re/go v0.8.0 + dappco.re/go v0.8.0 + dappco.re/go/ws v0.2.0 // indirect + dappco.re/go/process v0.3.0 )` - assert.Equal(t, []coreDep{ - {module: "dappco.re/go/core", repo: "go", dir: "core-go"}, + core.AssertEqual(t, []coreDep{ + {module: "dappco.re/go", repo: "go", dir: "core-go"}, {module: "dappco.re/go/process", repo: "go-process", dir: "core-go-process"}, }, parseCoreDeps(goMod)) } func TestDeps_ForgeSSHURL_Good(t *testing.T) { - assert.Equal(t, "ssh://git@forge.lthn.ai:2223/core/go-io.git", forgeSSHURL("core", "go-io")) + url := forgeSSHURL("core", "go-io") + core.AssertEqual(t, "ssh://git@forge.lthn.ai:2223/core/go-io.git", url) + core.AssertContains(t, url, "/core/go-io.git") } func TestDeps_CloneWorkspaceDeps_Bad_NoGoMod(t *testing.T) { @@ -74,7 +75,7 @@ func TestDeps_CloneWorkspaceDeps_Bad_NoGoMod(t *testing.T) { t.Fatalf("clone workspace deps: %v", err) } - assert.False(t, fs.IsFile(core.JoinPath(wsDir, "go.work"))) + core.AssertFalse(t, fs.IsFile(core.JoinPath(wsDir, "go.work"))) } func TestDeps_CloneWorkspaceDeps_Ugly_NoDirectCoreDeps(t *testing.T) { @@ -89,7 +90,7 @@ func TestDeps_CloneWorkspaceDeps_Ugly_NoDirectCoreDeps(t *testing.T) { go 1.26.0 require ( - dappco.re/go/core/process v0.3.0 // indirect + dappco.re/go/process v0.3.0 // indirect github.com/stretchr/testify v1.11.1 )` if r := fs.Write(core.JoinPath(repoDir, "go.mod"), goMod); !r.OK { @@ -101,5 +102,5 @@ require ( t.Fatalf("clone workspace deps: %v", err) } - assert.False(t, fs.IsFile(core.JoinPath(wsDir, "go.work"))) + core.AssertFalse(t, fs.IsFile(core.JoinPath(wsDir, "go.work"))) } diff --git a/pkg/agentic/dispatch.go b/pkg/agentic/dispatch.go index 267c5b9f..44fbe522 100644 --- a/pkg/agentic/dispatch.go +++ b/pkg/agentic/dispatch.go @@ -6,8 +6,8 @@ import ( "context" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" coremcp "dappco.re/go/mcp/pkg/mcp" "dappco.re/go/process" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -756,6 +756,12 @@ func (m *agentCompletionMonitor) run(_ context.Context, _ core.Options) core.Res <-m.process.Done() info := m.process.Info() + if (info.ExitCode != 0 || info.Status == process.StatusKilled || info.Status == process.StatusFailed) && m.workspaceDir != "" { + deadline := time.Now().Add(100 * time.Millisecond) + for dispatchTimeoutReasonFromWorkspace(m.workspaceDir) == "" && time.Now().Before(deadline) { + time.Sleep(5 * time.Millisecond) + } + } m.service.onAgentComplete(m.agent, m.workspaceDir, m.outputFile, info.ExitCode, string(info.Status), m.process.Output()) return core.Result{OK: true} } diff --git a/pkg/agentic/dispatch_example_test.go b/pkg/agentic/dispatch_example_test.go index f911acd9..ee57ae21 100644 --- a/pkg/agentic/dispatch_example_test.go +++ b/pkg/agentic/dispatch_example_test.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func Example_detectFinalStatus() { diff --git a/pkg/agentic/dispatch_runtime_test.go b/pkg/agentic/dispatch_runtime_test.go index 8dc2148b..d2234e3a 100644 --- a/pkg/agentic/dispatch_runtime_test.go +++ b/pkg/agentic/dispatch_runtime_test.go @@ -6,27 +6,31 @@ import ( "strings" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" + core "dappco.re/go" ) // --- containerRuntimeBinary --- func TestDispatchRuntime_ContainerRuntimeBinary_Good(t *testing.T) { - assert.Equal(t, "container", containerRuntimeBinary(RuntimeApple)) - assert.Equal(t, "docker", containerRuntimeBinary(RuntimeDocker)) - assert.Equal(t, "podman", containerRuntimeBinary(RuntimePodman)) + core.AssertEqual(t, "container", containerRuntimeBinary(RuntimeApple)) + core.AssertEqual(t, "docker", containerRuntimeBinary(RuntimeDocker)) + core.AssertEqual(t, "podman", containerRuntimeBinary(RuntimePodman)) } func TestDispatchRuntime_ContainerRuntimeBinary_Bad(t *testing.T) { // Unknown runtime falls back to docker so dispatch never silently breaks. - assert.Equal(t, "docker", containerRuntimeBinary("")) - assert.Equal(t, "docker", containerRuntimeBinary("kubernetes")) + empty := containerRuntimeBinary("") + unknown := containerRuntimeBinary("kubernetes") + core.AssertEqual(t, "docker", empty) + core.AssertEqual(t, "docker", unknown) } func TestDispatchRuntime_ContainerRuntimeBinary_Ugly(t *testing.T) { // Whitespace-laden runtime name is treated as unknown; docker fallback wins. - assert.Equal(t, "docker", containerRuntimeBinary(" apple ")) + runtimeName := " apple " + result := containerRuntimeBinary(runtimeName) + core.AssertEqual(t, "docker", result) + core.AssertNotEqual(t, "container", result) } // --- runtimeAvailable --- @@ -35,21 +39,23 @@ func TestDispatchRuntime_RuntimeAvailable_Good(t *testing.T) { // Inspect only the failure path that doesn't depend on host binaries. // Apple Container is by definition unavailable on non-darwin. if !isDarwin() { - assert.False(t, runtimeAvailable(RuntimeApple)) + core.AssertFalse(t, runtimeAvailable(RuntimeApple)) } } func TestDispatchRuntime_RuntimeAvailable_Bad(t *testing.T) { // Unknown runtimes are never available. - assert.False(t, runtimeAvailable("")) - assert.False(t, runtimeAvailable("kubernetes")) + empty := runtimeAvailable("") + unknown := runtimeAvailable("kubernetes") + core.AssertFalse(t, empty) + core.AssertFalse(t, unknown) } func TestDispatchRuntime_RuntimeAvailable_Ugly(t *testing.T) { // Apple Container on non-macOS hosts is always unavailable, regardless of // whether a binary called "container" happens to be on PATH. if !isDarwin() { - assert.False(t, runtimeAvailable(RuntimeApple)) + core.AssertFalse(t, runtimeAvailable(RuntimeApple)) } } @@ -60,20 +66,22 @@ func TestDispatchRuntime_ResolveContainerRuntime_Good(t *testing.T) { // hard fallback, but the function may surface apple/podman when those // binaries exist on the test host). resolved := resolveContainerRuntime("") - assert.Contains(t, []string{RuntimeApple, RuntimeDocker, RuntimePodman}, resolved) + core.AssertNotEmpty(t, resolved) + core.AssertContains(t, []string{RuntimeApple, RuntimeDocker, RuntimePodman}, resolved) } func TestDispatchRuntime_ResolveContainerRuntime_Bad(t *testing.T) { // An unknown runtime preference still resolves to a known runtime. resolved := resolveContainerRuntime("kubernetes") - assert.Contains(t, []string{RuntimeApple, RuntimeDocker, RuntimePodman}, resolved) + core.AssertNotEmpty(t, resolved) + core.AssertContains(t, []string{RuntimeApple, RuntimeDocker, RuntimePodman}, resolved) } func TestDispatchRuntime_ResolveContainerRuntime_Ugly(t *testing.T) { // Apple preference on non-darwin host falls back to a non-apple runtime. if !isDarwin() { resolved := resolveContainerRuntime(RuntimeApple) - assert.NotEqual(t, RuntimeApple, resolved) + core.AssertNotEqual(t, RuntimeApple, resolved) } } @@ -85,10 +93,10 @@ func TestDispatchRuntime_ContainerCommandFor_Good(t *testing.T) { // Docker runtime emits docker binary and includes host-gateway alias. cmd, args := containerCommandFor(RuntimeDocker, "core-dev", false, "codex", []string{"exec"}, "/ws", "/ws/.meta") - assert.Equal(t, "docker", cmd) + core.AssertEqual(t, "docker", cmd) joined := strings.Join(args, " ") - assert.Contains(t, joined, "--add-host=host.docker.internal:host-gateway") - assert.Contains(t, joined, "core-dev") + core.AssertContains(t, joined, "--add-host=host.docker.internal:host-gateway") + core.AssertContains(t, joined, "core-dev") } func TestDispatchRuntime_ContainerCommandFor_Bad(t *testing.T) { @@ -97,8 +105,8 @@ func TestDispatchRuntime_ContainerCommandFor_Bad(t *testing.T) { // Empty image resolves to the default rather than passing "" to docker. cmd, args := containerCommandFor(RuntimeDocker, "", false, "codex", nil, "/ws", "/ws/.meta") - assert.Equal(t, "docker", cmd) - assert.Contains(t, args, defaultDockerImage) + core.AssertEqual(t, "docker", cmd) + core.AssertContains(t, args, defaultDockerImage) } func TestDispatchRuntime_ContainerCommandFor_Ugly(t *testing.T) { @@ -108,21 +116,21 @@ func TestDispatchRuntime_ContainerCommandFor_Ugly(t *testing.T) { // Apple runtime emits the `container` binary and SKIPS the host-gateway // alias because Apple Containers don't support `--add-host=host-gateway`. cmd, args := containerCommandFor(RuntimeApple, "core-dev", false, "codex", []string{"exec"}, "/ws", "/ws/.meta") - assert.Equal(t, "container", cmd) + core.AssertEqual(t, "container", cmd) joined := strings.Join(args, " ") - assert.NotContains(t, joined, "--add-host=host.docker.internal:host-gateway") + core.AssertNotContains(t, joined, "--add-host=host.docker.internal:host-gateway") // Podman runtime emits the `podman` binary. cmd2, _ := containerCommandFor(RuntimePodman, "core-dev", false, "codex", []string{"exec"}, "/ws", "/ws/.meta") - assert.Equal(t, "podman", cmd2) + core.AssertEqual(t, "podman", cmd2) // GPU passthrough on docker emits `--gpus=all`. _, gpuArgs := containerCommandFor(RuntimeDocker, "core-dev", true, "codex", []string{"exec"}, "/ws", "/ws/.meta") - assert.Contains(t, strings.Join(gpuArgs, " "), "--gpus=all") + core.AssertContains(t, strings.Join(gpuArgs, " "), "--gpus=all") // GPU passthrough on apple emits `--gpu=metal` for Metal passthrough. _, appleGPUArgs := containerCommandFor(RuntimeApple, "core-dev", true, "codex", []string{"exec"}, "/ws", "/ws/.meta") - assert.Contains(t, strings.Join(appleGPUArgs, " "), "--gpu=metal") + core.AssertContains(t, strings.Join(appleGPUArgs, " "), "--gpu=metal") } // --- dispatchRuntime / dispatchImage / dispatchGPU --- @@ -132,14 +140,14 @@ func TestDispatchRuntime_DispatchRuntime_Good(t *testing.T) { c := core.New() c.Config().Set("agents.dispatch", DispatchConfig{Runtime: "podman"}) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} - assert.Equal(t, "podman", s.dispatchRuntime()) + core.AssertEqual(t, "podman", s.dispatchRuntime()) } func TestDispatchRuntime_DispatchRuntime_Bad(t *testing.T) { t.Setenv("CORE_AGENT_RUNTIME", "") // Nil subsystem returns the auto default. var s *PrepSubsystem - assert.Equal(t, RuntimeAuto, s.dispatchRuntime()) + core.AssertEqual(t, RuntimeAuto, s.dispatchRuntime()) } func TestDispatchRuntime_DispatchRuntime_Ugly(t *testing.T) { @@ -148,7 +156,7 @@ func TestDispatchRuntime_DispatchRuntime_Ugly(t *testing.T) { c := core.New() c.Config().Set("agents.dispatch", DispatchConfig{Runtime: "podman"}) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} - assert.Equal(t, "apple", s.dispatchRuntime()) + core.AssertEqual(t, "apple", s.dispatchRuntime()) } func TestDispatchRuntime_DispatchImage_Good(t *testing.T) { @@ -156,14 +164,14 @@ func TestDispatchRuntime_DispatchImage_Good(t *testing.T) { c := core.New() c.Config().Set("agents.dispatch", DispatchConfig{Image: "core-ml"}) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} - assert.Equal(t, "core-ml", s.dispatchImage()) + core.AssertEqual(t, "core-ml", s.dispatchImage()) } func TestDispatchRuntime_DispatchImage_Bad(t *testing.T) { t.Setenv("AGENT_DOCKER_IMAGE", "") // Nil subsystem falls back to the default image. var s *PrepSubsystem - assert.Equal(t, defaultDockerImage, s.dispatchImage()) + core.AssertEqual(t, defaultDockerImage, s.dispatchImage()) } func TestDispatchRuntime_DispatchImage_Ugly(t *testing.T) { @@ -172,27 +180,29 @@ func TestDispatchRuntime_DispatchImage_Ugly(t *testing.T) { c := core.New() c.Config().Set("agents.dispatch", DispatchConfig{Image: "core-ml"}) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} - assert.Equal(t, "ad-hoc-image", s.dispatchImage()) + core.AssertEqual(t, "ad-hoc-image", s.dispatchImage()) } func TestDispatchRuntime_DispatchGPU_Good(t *testing.T) { c := core.New() c.Config().Set("agents.dispatch", DispatchConfig{GPU: true}) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} - assert.True(t, s.dispatchGPU()) + core.AssertTrue(t, s.dispatchGPU()) } func TestDispatchRuntime_DispatchGPU_Bad(t *testing.T) { // Nil subsystem returns false (GPU off by default). var s *PrepSubsystem - assert.False(t, s.dispatchGPU()) + enabled := s.dispatchGPU() + core.AssertFalse(t, enabled) + core.AssertEqual(t, false, enabled) } func TestDispatchRuntime_DispatchGPU_Ugly(t *testing.T) { // Missing dispatch config returns false instead of panicking. c := core.New() s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{})} - assert.False(t, s.dispatchGPU()) + core.AssertFalse(t, s.dispatchGPU()) } // isDarwin checks the host operating system without importing runtime in the diff --git a/pkg/agentic/dispatch_sync.go b/pkg/agentic/dispatch_sync.go index 62717891..34e2f5ec 100644 --- a/pkg/agentic/dispatch_sync.go +++ b/pkg/agentic/dispatch_sync.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" ) // input := agentic.DispatchSyncInput{Repo: "go-crypt", Agent: "codex:gpt-5.3-codex-spark", Task: "fix it", Issue: 7} diff --git a/pkg/agentic/dispatch_sync_example_test.go b/pkg/agentic/dispatch_sync_example_test.go index be577915..f2fe23de 100644 --- a/pkg/agentic/dispatch_sync_example_test.go +++ b/pkg/agentic/dispatch_sync_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func Example_containerCommand() { cmd, args := containerCommand("codex", []string{"--model", "gpt-5.4"}, "/workspace/task-5", "/workspace/task-5/.meta") diff --git a/pkg/agentic/dispatch_sync_test.go b/pkg/agentic/dispatch_sync_test.go index fd6a8765..910ebccc 100644 --- a/pkg/agentic/dispatch_sync_test.go +++ b/pkg/agentic/dispatch_sync_test.go @@ -7,29 +7,27 @@ import ( "testing" "time" - core "dappco.re/go/core" + core "dappco.re/go" "github.com/modelcontextprotocol/go-sdk/mcp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestDispatchsync_ContainerCommand_Good(t *testing.T) { cmd, args := containerCommand("codex", []string{"--model", "gpt-5.4"}, "/workspace/task-5", "/workspace/task-5/.meta") - assert.Equal(t, "docker", cmd) - assert.Contains(t, args, "run") - assert.Contains(t, args, "/workspace/task-5:/workspace") - assert.Contains(t, args, "/workspace/task-5/.meta:/workspace/.meta") - assert.Contains(t, args, "/workspace/repo") + core.AssertEqual(t, "docker", cmd) + core.AssertContains(t, args, "run") + core.AssertContains(t, args, "/workspace/task-5:/workspace") + core.AssertContains(t, args, "/workspace/task-5/.meta:/workspace/.meta") + core.AssertContains(t, args, "/workspace/repo") } func TestDispatchsync_ContainerCommand_Bad_UnknownAgent(t *testing.T) { cmd, args := containerCommand("unknown", nil, "/workspace/task-5", "/workspace/task-5/.meta") - assert.Equal(t, "docker", cmd) - assert.NotEmpty(t, args) + core.AssertEqual(t, "docker", cmd) + core.AssertNotEmpty(t, args) } func TestDispatchsync_ContainerCommand_Ugly_EmptyArgs(t *testing.T) { - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { containerCommand("codex", nil, "", "") }) } @@ -42,14 +40,14 @@ func TestDispatchsync_HandleDispatchSync_Good_Completed(t *testing.T) { s := &PrepSubsystem{dispatchSyncTick: 10 * time.Millisecond} s.dispatchSyncPrep = func(ctx context.Context, _ *mcp.CallToolRequest, input PrepInput) (*mcp.CallToolResult, PrepOutput, error) { - require.Equal(t, "core", input.Org) - require.Equal(t, "go-io", input.Repo) - require.Equal(t, "codex", input.Agent) - require.Equal(t, "Fix tests", input.Task) - require.Equal(t, 7, input.Issue) - - require.True(t, fs.EnsureDir(workspaceDir).OK) - require.True(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ + core.AssertEqual(t, "core", input.Org) + core.AssertEqual(t, "go-io", input.Repo) + core.AssertEqual(t, "codex", input.Agent) + core.AssertEqual(t, "Fix tests", input.Task) + core.AssertEqual(t, 7, input.Issue) + + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ Status: "completed", PRURL: "https://forge.test/core/go-io/pulls/7", })).OK) @@ -62,9 +60,9 @@ func TestDispatchsync_HandleDispatchSync_Good_Completed(t *testing.T) { }, nil } s.dispatchSyncSpawn = func(agent, prompt, dir string) (int, string, string, error) { - require.Equal(t, "codex", agent) - require.Equal(t, "prompt", prompt) - require.Equal(t, workspaceDir, dir) + core.AssertEqual(t, "codex", agent) + core.AssertEqual(t, "prompt", prompt) + core.AssertEqual(t, workspaceDir, dir) return 321, "process-321", core.JoinPath(dir, ".meta", "agent.log"), nil } @@ -76,12 +74,12 @@ func TestDispatchsync_HandleDispatchSync_Good_Completed(t *testing.T) { core.Option{Key: "issue", Value: "7"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(DispatchSyncResult) - require.True(t, ok) - assert.True(t, output.OK) - assert.Equal(t, "completed", output.Status) - assert.Equal(t, "https://forge.test/core/go-io/pulls/7", output.PRURL) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.OK) + core.AssertEqual(t, "completed", output.Status) + core.AssertEqual(t, "https://forge.test/core/go-io/pulls/7", output.PRURL) } func TestDispatchsync_HandleDispatchSync_Bad_PrepFailure(t *testing.T) { @@ -95,9 +93,9 @@ func TestDispatchsync_HandleDispatchSync_Bad_PrepFailure(t *testing.T) { core.Option{Key: "task", Value: "Fix tests"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "prep workspace failed") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "prep workspace failed") } func TestDispatchsync_HandleDispatchSync_Bad_PrepIncomplete(t *testing.T) { @@ -113,9 +111,9 @@ func TestDispatchsync_HandleDispatchSync_Bad_PrepIncomplete(t *testing.T) { core.Option{Key: "task", Value: "Fix tests"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "prep failed") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "prep failed") } func TestDispatchsync_HandleDispatchSync_Ugly_SpawnFailure(t *testing.T) { @@ -126,8 +124,8 @@ func TestDispatchsync_HandleDispatchSync_Ugly_SpawnFailure(t *testing.T) { s := &PrepSubsystem{dispatchSyncTick: 10 * time.Millisecond} s.dispatchSyncPrep = func(context.Context, *mcp.CallToolRequest, PrepInput) (*mcp.CallToolResult, PrepOutput, error) { - require.True(t, fs.EnsureDir(workspaceDir).OK) - require.True(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(&WorkspaceStatus{ Status: "running", })).OK) @@ -139,7 +137,7 @@ func TestDispatchsync_HandleDispatchSync_Ugly_SpawnFailure(t *testing.T) { }, nil } s.dispatchSyncSpawn = func(agent, prompt, dir string) (int, string, string, error) { - require.Equal(t, "codex", agent) + core.AssertEqual(t, "codex", agent) return 0, "", "", core.E("spawn", "boom", nil) } @@ -149,7 +147,7 @@ func TestDispatchsync_HandleDispatchSync_Ugly_SpawnFailure(t *testing.T) { core.Option{Key: "task", Value: "Fix tests"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "spawn agent failed") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "spawn agent failed") } diff --git a/pkg/agentic/dispatch_test.go b/pkg/agentic/dispatch_test.go index 8a9f6e10..01c05c38 100644 --- a/pkg/agentic/dispatch_test.go +++ b/pkg/agentic/dispatch_test.go @@ -9,12 +9,10 @@ import ( "testing" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" "dappco.re/go/forge" "dappco.re/go/process" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type fakeCompletionProcess struct { @@ -40,21 +38,29 @@ func (p *fakeCompletionProcess) Output() string { return p.output } // --- agentOutputFile --- func TestDispatch_AgentOutputFile_Good(t *testing.T) { - assert.Contains(t, agentOutputFile("/ws", "codex"), ".meta/agent-codex.log") - assert.Contains(t, agentOutputFile("/ws", "claude:opus"), ".meta/agent-claude.log") - assert.Contains(t, agentOutputFile("/ws", "gemini:flash"), ".meta/agent-gemini.log") + core.AssertContains(t, agentOutputFile("/ws", "codex"), ".meta/agent-codex.log") + core.AssertContains(t, agentOutputFile("/ws", "claude:opus"), ".meta/agent-claude.log") + core.AssertContains(t, agentOutputFile("/ws", "gemini:flash"), ".meta/agent-gemini.log") } func TestDispatch_AgentOutputFile_Bad(t *testing.T) { // Empty agent — still produces a path (no crash) result := agentOutputFile("/ws", "") - assert.Contains(t, result, ".meta/agent-.log") + core.AssertContains( + t, + result, + ".meta/agent-.log", + ) } func TestDispatch_AgentOutputFile_Ugly(t *testing.T) { // Agent with multiple colons — only splits on first result := agentOutputFile("/ws", "claude:opus:latest") - assert.Contains(t, result, "agent-claude.log") + core.AssertContains( + t, + result, + "agent-claude.log", + ) } // --- detectFinalStatus --- @@ -64,8 +70,8 @@ func TestDispatch_DetectFinalStatus_Good(t *testing.T) { // Clean exit = completed status, question := detectFinalStatus(dir, 0, "completed") - assert.Equal(t, "completed", status) - assert.Empty(t, question) + core.AssertEqual(t, "completed", status) + core.AssertEmpty(t, question) } func TestDispatch_DetectFinalStatus_Bad(t *testing.T) { @@ -73,16 +79,16 @@ func TestDispatch_DetectFinalStatus_Bad(t *testing.T) { // Non-zero exit code status, question := detectFinalStatus(dir, 1, "completed") - assert.Equal(t, "failed", status) - assert.Contains(t, question, "code 1") + core.AssertEqual(t, "failed", status) + core.AssertContains(t, question, "code 1") // Process killed status2, _ := detectFinalStatus(dir, 0, "killed") - assert.Equal(t, "failed", status2) + core.AssertEqual(t, "failed", status2) // Process status "failed" status3, _ := detectFinalStatus(dir, 0, "failed") - assert.Equal(t, "failed", status3) + core.AssertEqual(t, "failed", status3) } func TestDispatch_DetectFinalStatus_Ugly(t *testing.T) { @@ -91,13 +97,13 @@ func TestDispatch_DetectFinalStatus_Ugly(t *testing.T) { // BLOCKED.md exists but is whitespace only — NOT blocked fs.Write(core.JoinPath(dir, "BLOCKED.md"), " \n ") status, _ := detectFinalStatus(dir, 0, "completed") - assert.Equal(t, "completed", status) + core.AssertEqual(t, "completed", status) // BLOCKED.md takes precedence over non-zero exit fs.Write(core.JoinPath(dir, "BLOCKED.md"), "Need credentials") status2, question2 := detectFinalStatus(dir, 1, "failed") - assert.Equal(t, "blocked", status2) - assert.Equal(t, "Need credentials", question2) + core.AssertEqual(t, "blocked", status2) + core.AssertEqual(t, "Need credentials", question2) } // --- trackFailureRate --- @@ -107,8 +113,8 @@ func TestDispatch_TrackFailureRate_Good(t *testing.T) { // Success resets count triggered := s.trackFailureRate("codex", "completed", time.Now().Add(-10*time.Second)) - assert.False(t, triggered) - assert.Equal(t, 0, s.failCount["codex"]) + core.AssertFalse(t, triggered) + core.AssertEqual(t, 0, s.failCount["codex"]) } func TestDispatch_TrackFailureRate_Bad(t *testing.T) { @@ -125,11 +131,11 @@ func TestDispatch_TrackFailureRate_Bad(t *testing.T) { // 3rd fast failure triggers backoff triggered := s.trackFailureRate("codex", "failed", time.Now().Add(-10*time.Second)) - assert.True(t, triggered) - assert.True(t, time.Now().Before(s.backoff["codex"])) - require.Len(t, captured, 1) - assert.Equal(t, "codex", captured[0].Pool) - assert.Equal(t, "30m0s", captured[0].Duration) + core.AssertTrue(t, triggered) + core.AssertTrue(t, time.Now().Before(s.backoff["codex"])) + core.AssertLen(t, captured, 1) + core.AssertEqual(t, "codex", captured[0].Pool) + core.AssertEqual(t, "30m0s", captured[0].Duration) } func TestDispatch_TrackFailureRate_Ugly(t *testing.T) { @@ -138,11 +144,11 @@ func TestDispatch_TrackFailureRate_Ugly(t *testing.T) { // Slow failure (>60s) resets count instead of incrementing s.failCount["codex"] = 2 s.trackFailureRate("codex", "failed", time.Now().Add(-5*time.Minute)) - assert.Equal(t, 0, s.failCount["codex"]) + core.AssertEqual(t, 0, s.failCount["codex"]) // Model variant tracks by base pool s.trackFailureRate("codex:gpt-5.4", "failed", time.Now().Add(-10*time.Second)) - assert.Equal(t, 1, s.failCount["codex"]) + core.AssertEqual(t, 1, s.failCount["codex"]) } // --- startIssueTracking --- @@ -200,6 +206,7 @@ func TestDispatch_StopIssueTracking_Good(t *testing.T) { func TestDispatch_StopIssueTracking_Bad(t *testing.T) { s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), forge: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)} s.stopIssueTracking(t.TempDir()) + core.AssertNil(t, s.forge) } func TestDispatch_StopIssueTracking_Ugly(t *testing.T) { @@ -231,6 +238,7 @@ func TestDispatch_BroadcastStart_Bad(t *testing.T) { // No Core — should not panic s := &PrepSubsystem{ServiceRuntime: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)} s.broadcastStart("codex", t.TempDir()) + core.AssertNil(t, s.ServiceRuntime) } func TestDispatch_BroadcastStart_Ugly(t *testing.T) { @@ -258,6 +266,7 @@ func TestDispatch_BroadcastComplete_Good(t *testing.T) { func TestDispatch_BroadcastComplete_Bad(t *testing.T) { s := &PrepSubsystem{ServiceRuntime: nil, backoff: make(map[string]time.Time), failCount: make(map[string]int)} s.broadcastComplete("codex", t.TempDir(), "failed") + core.AssertNil(t, s.ServiceRuntime) } func TestDispatch_BroadcastComplete_Ugly(t *testing.T) { @@ -299,15 +308,15 @@ func TestDispatch_AgentCompletionMonitor_Good(t *testing.T) { } r := monitor.run(context.Background(), core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) updated := mustReadStatus(t, wsDir) - assert.Equal(t, "completed", updated.Status) - assert.Equal(t, 0, updated.PID) + core.AssertEqual(t, "completed", updated.Status) + core.AssertEqual(t, 0, updated.PID) output := fs.Read(core.JoinPath(metaDir, "agent-codex.log")) - require.True(t, output.OK) - assert.Equal(t, "monitor output", output.Value.(string)) + core.RequireTrue(t, output.OK) + core.AssertEqual(t, "monitor output", output.Value.(string)) } func TestDispatch_AgentCompletionMonitor_Bad(t *testing.T) { @@ -319,9 +328,9 @@ func TestDispatch_AgentCompletionMonitor_Bad(t *testing.T) { } r := monitor.run(context.Background(), core.NewOptions()) - assert.False(t, r.OK) - require.Error(t, r.Value.(error)) - assert.Contains(t, r.Value.(error).Error(), "process is required") + core.AssertFalse(t, r.OK) + core.AssertError(t, r.Value.(error)) + core.AssertContains(t, r.Value.(error).Error(), "process is required") } func TestDispatch_AgentCompletionMonitor_Ugly(t *testing.T) { @@ -355,12 +364,12 @@ func TestDispatch_AgentCompletionMonitor_Ugly(t *testing.T) { } r := monitor.run(context.Background(), core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) updated := mustReadStatus(t, wsDir) - assert.Equal(t, "blocked", updated.Status) - assert.Equal(t, "Need credentials", updated.Question) - assert.False(t, fs.Exists(core.JoinPath(metaDir, "agent-codex.log"))) + core.AssertEqual(t, "blocked", updated.Status) + core.AssertEqual(t, "Need credentials", updated.Question) + core.AssertFalse(t, fs.Exists(core.JoinPath(metaDir, "agent-codex.log"))) } // --- onAgentComplete --- @@ -383,12 +392,12 @@ func TestDispatch_OnAgentComplete_Good(t *testing.T) { s.onAgentComplete("codex", wsDir, outputFile, 0, "completed", "test output") updated := mustReadStatus(t, wsDir) - assert.Equal(t, "completed", updated.Status) - assert.Equal(t, 0, updated.PID) + core.AssertEqual(t, "completed", updated.Status) + core.AssertEqual(t, 0, updated.PID) r := fs.Read(outputFile) - assert.True(t, r.OK) - assert.Equal(t, "test output", r.Value.(string)) + core.AssertTrue(t, r.OK) + core.AssertEqual(t, "test output", r.Value.(string)) } func TestDispatch_OnAgentComplete_Bad(t *testing.T) { @@ -408,8 +417,8 @@ func TestDispatch_OnAgentComplete_Bad(t *testing.T) { s.onAgentComplete("codex", wsDir, core.JoinPath(metaDir, "agent-codex.log"), 1, "failed", "error") updated := mustReadStatus(t, wsDir) - assert.Equal(t, "failed", updated.Status) - assert.Contains(t, updated.Question, "code 1") + core.AssertEqual(t, "failed", updated.Status) + core.AssertContains(t, updated.Question, "code 1") } func TestDispatch_OnAgentComplete_Ugly(t *testing.T) { @@ -430,11 +439,11 @@ func TestDispatch_OnAgentComplete_Ugly(t *testing.T) { s.onAgentComplete("codex", wsDir, core.JoinPath(metaDir, "agent-codex.log"), 0, "completed", "") updated := mustReadStatus(t, wsDir) - assert.Equal(t, "blocked", updated.Status) - assert.Equal(t, "Need credentials", updated.Question) + core.AssertEqual(t, "blocked", updated.Status) + core.AssertEqual(t, "Need credentials", updated.Question) // Empty output should NOT create log file - assert.False(t, fs.Exists(core.JoinPath(metaDir, "agent-codex.log"))) + core.AssertFalse(t, fs.Exists(core.JoinPath(metaDir, "agent-codex.log"))) } func TestDispatch_Run_Bad_Timeout(t *testing.T) { @@ -444,8 +453,8 @@ func TestDispatch_Run_Bad_Timeout(t *testing.T) { wsDir := core.JoinPath(root, "ws-timeout") repoDir := core.JoinPath(wsDir, "repo") metaDir := core.JoinPath(wsDir, ".meta") - require.True(t, fs.EnsureDir(repoDir).OK) - require.True(t, fs.EnsureDir(metaDir).OK) + core.RequireTrue(t, fs.EnsureDir(repoDir).OK) + core.RequireTrue(t, fs.EnsureDir(metaDir).OK) st := &WorkspaceStatus{ Status: "running", @@ -453,22 +462,22 @@ func TestDispatch_Run_Bad_Timeout(t *testing.T) { Repo: "go-io", StartedAt: time.Now(), } - require.NoError(t, writeStatus(wsDir, st)) + core.RequireNoError(t, writeStatus(wsDir, st)) processResult := testCore.Service("process") - require.True(t, processResult.OK) + core.RequireTrue(t, processResult.OK) procSvc, ok := processResult.Value.(*process.Service) - require.True(t, ok) + core.RequireTrue(t, ok) timeout := 100 * time.Millisecond opts := dispatchRunOptions("sleep", []string{"60"}, repoDir, timeout) - assert.Equal(t, timeout, opts.Timeout) - assert.Equal(t, dispatchGracePeriod, opts.GracePeriod) - assert.True(t, opts.KillGroup) - assert.True(t, opts.Detach) + core.AssertEqual(t, timeout, opts.Timeout) + core.AssertEqual(t, dispatchGracePeriod, opts.GracePeriod) + core.AssertTrue(t, opts.KillGroup) + core.AssertTrue(t, opts.Detach) proc, err := procSvc.StartWithOptions(context.Background(), opts) - require.NoError(t, err) + core.RequireNoError(t, err) proc.CloseStdin() s := newPrepWithProcess() @@ -484,24 +493,24 @@ func TestDispatch_Run_Bad_Timeout(t *testing.T) { } r := monitor.run(context.Background(), core.NewOptions()) - assert.True(t, r.OK) + core.AssertTrue(t, r.OK) info := proc.Info() - assert.Equal(t, process.StatusKilled, info.Status) + core.AssertEqual(t, process.StatusKilled, info.Status) updated := mustReadStatus(t, wsDir) - assert.Equal(t, "failed", updated.Status) - assert.Equal(t, dispatchTimeoutReason(timeout), updated.Question) - assert.Equal(t, 0, updated.PID) + core.AssertEqual(t, "failed", updated.Status) + core.AssertEqual(t, dispatchTimeoutReason(timeout), updated.Question) + core.AssertEqual(t, 0, updated.PID) registryResult := s.workspaces.Get(WorkspaceName(wsDir)) - require.True(t, registryResult.OK) + core.RequireTrue(t, registryResult.OK) registryStatus, ok := registryResult.Value.(*WorkspaceStatus) - require.True(t, ok) - assert.Equal(t, "failed", registryStatus.Status) - assert.Equal(t, dispatchTimeoutReason(timeout), registryStatus.Question) + core.RequireTrue(t, ok) + core.AssertEqual(t, "failed", registryStatus.Status) + core.AssertEqual(t, dispatchTimeoutReason(timeout), registryStatus.Question) - assert.False(t, fs.Exists(workspaceTimeoutPath(wsDir))) + core.AssertFalse(t, fs.Exists(workspaceTimeoutPath(wsDir))) } // --- runQA --- @@ -514,7 +523,7 @@ func TestDispatch_RunQA_Good(t *testing.T) { fs.Write(core.JoinPath(repoDir, "main.go"), "package main\nfunc main() {}\n") s := newPrepWithProcess() - assert.True(t, s.runQA(wsDir)) + core.AssertTrue(t, s.runQA(wsDir)) } func TestDispatch_RunQA_Bad(t *testing.T) { @@ -527,7 +536,7 @@ func TestDispatch_RunQA_Bad(t *testing.T) { fs.Write(core.JoinPath(repoDir, "main.go"), "package main\nfunc main( {\n}\n") s := newPrepWithProcess() - assert.False(t, s.runQA(wsDir)) + core.AssertFalse(t, s.runQA(wsDir)) // PHP project — composer not available wsDir2 := t.TempDir() @@ -535,7 +544,7 @@ func TestDispatch_RunQA_Bad(t *testing.T) { fs.EnsureDir(repoDir2) fs.Write(core.JoinPath(repoDir2, "composer.json"), `{"name":"test"}`) - assert.False(t, s.runQA(wsDir2)) + core.AssertFalse(t, s.runQA(wsDir2)) } func TestDispatch_RunQA_Ugly(t *testing.T) { @@ -544,7 +553,7 @@ func TestDispatch_RunQA_Ugly(t *testing.T) { fs.EnsureDir(core.JoinPath(wsDir, "repo")) s := newPrepWithProcess() - assert.True(t, s.runQA(wsDir)) + core.AssertTrue(t, s.runQA(wsDir)) // Go vet failure (compiles but bad printf) wsDir2 := t.TempDir() @@ -552,7 +561,7 @@ func TestDispatch_RunQA_Ugly(t *testing.T) { fs.EnsureDir(repoDir2) fs.Write(core.JoinPath(repoDir2, "go.mod"), "module testmod\n\ngo 1.22\n") fs.Write(core.JoinPath(repoDir2, "main.go"), "package main\nimport \"fmt\"\nfunc main() { fmt.Printf(\"%d\", \"x\") }\n") - assert.False(t, s.runQA(wsDir2)) + core.AssertFalse(t, s.runQA(wsDir2)) // Node project — npm install likely fails wsDir3 := t.TempDir() @@ -588,10 +597,10 @@ func TestDispatch_Dispatch_Good(t *testing.T) { _, out, err := s.dispatch(context.Background(), nil, DispatchInput{ Repo: "go-io", Task: "Fix stuff", Issue: 42, DryRun: true, }) - require.NoError(t, err) - assert.True(t, out.Success) - assert.Equal(t, "codex", out.Agent) - assert.NotEmpty(t, out.Prompt) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + core.AssertEqual(t, "codex", out.Agent) + core.AssertNotEmpty(t, out.Prompt) } func TestDispatch_Dispatch_Bad(t *testing.T) { @@ -599,13 +608,13 @@ func TestDispatch_Dispatch_Bad(t *testing.T) { // No repo _, _, err := s.dispatch(context.Background(), nil, DispatchInput{Task: "do"}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "repo is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "repo is required") // No task _, _, err = s.dispatch(context.Background(), nil, DispatchInput{Repo: "go-io"}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "task is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "task is required") } func TestDispatch_Dispatch_Ugly(t *testing.T) { @@ -617,8 +626,8 @@ func TestDispatch_Dispatch_Ugly(t *testing.T) { _, _, err := s.dispatch(context.Background(), nil, DispatchInput{ Repo: "nonexistent", Task: "do", Issue: 1, }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "prep workspace failed") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "prep workspace failed") } // --- workspaceDir --- @@ -628,23 +637,23 @@ func TestDispatch_WorkspaceDir_Good(t *testing.T) { setTestWorkspace(t, root) dir, err := workspaceDir("core", "go-io", PrepInput{Issue: 42}) - require.NoError(t, err) - assert.Contains(t, dir, "task-42") + core.RequireNoError(t, err) + core.AssertContains(t, dir, "task-42") dir2, _ := workspaceDir("core", "go-io", PrepInput{PR: 7}) - assert.Contains(t, dir2, "pr-7") + core.AssertContains(t, dir2, "pr-7") dir3, _ := workspaceDir("core", "go-io", PrepInput{Branch: "feat/new"}) - assert.Contains(t, dir3, "feat/new") + core.AssertContains(t, dir3, "feat/new") dir4, _ := workspaceDir("core", "go-io", PrepInput{Tag: "v1.0.0"}) - assert.Contains(t, dir4, "v1.0.0") + core.AssertContains(t, dir4, "v1.0.0") } func TestDispatch_WorkspaceDir_Bad(t *testing.T) { _, err := workspaceDir("core", "go-io", PrepInput{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "one of issue, pr, branch, or tag") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "one of issue, pr, branch, or tag") } func TestDispatch_WorkspaceDir_Ugly(t *testing.T) { @@ -653,8 +662,8 @@ func TestDispatch_WorkspaceDir_Ugly(t *testing.T) { // PR takes precedence when multiple set (first match) dir, err := workspaceDir("core", "go-io", PrepInput{PR: 3, Issue: 5}) - require.NoError(t, err) - assert.Contains(t, dir, "pr-3") + core.RequireNoError(t, err) + core.AssertContains(t, dir, "pr-3") } // --- containerCommand --- @@ -665,12 +674,12 @@ func TestDispatch_ContainerCommand_Bad(t *testing.T) { // Empty command string — docker still runs, just with no command after image cmd, args := containerCommand("", []string{}, "/ws", "/ws/.meta") - assert.Equal(t, "docker", cmd) - assert.Contains(t, args, "run") + core.AssertEqual(t, "docker", cmd) + core.AssertContains(t, args, "run") // The image should still be present in args - assert.Contains(t, args, defaultDockerImage) - assert.Contains(t, args, "/ws:/workspace") - assert.Contains(t, args, "/workspace/repo") + core.AssertContains(t, args, defaultDockerImage) + core.AssertContains(t, args, "/ws:/workspace") + core.AssertContains(t, args, "/workspace/repo") } // --- canDispatchAgent --- @@ -682,9 +691,9 @@ func TestDispatch_ContainerCommand_Bad(t *testing.T) { func TestDispatch_agentCommand_Good(t *testing.T) { command, args, err := agentCommand("codex:gpt-5.4-mini", "Implement AX-10 unit tests for Mantis #169") - require.NoError(t, err) - assert.Equal(t, "codex", command) - assert.Equal(t, []string{ + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", command) + core.AssertEqual(t, []string{ "exec", "--dangerously-bypass-approvals-and-sandbox", "-o", "../.meta/agent-codex.log", @@ -695,16 +704,16 @@ func TestDispatch_agentCommand_Good(t *testing.T) { func TestDispatch_agentCommand_Bad(t *testing.T) { command, args, err := agentCommand("mantis", "Investigate a failing dispatch") - require.Error(t, err) - assert.Contains(t, err.Error(), "unknown agent: mantis") - assert.Empty(t, command) - assert.Nil(t, args) + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "unknown agent: mantis") + core.AssertEmpty(t, command) + core.AssertNil(t, args) } func TestDispatch_agentCommand_Ugly(t *testing.T) { command, args, err := agentCommand("", "Investigate a failing dispatch") - require.Error(t, err) - assert.Contains(t, err.Error(), "unknown agent") - assert.Empty(t, command) - assert.Nil(t, args) + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "unknown agent") + core.AssertEmpty(t, command) + core.AssertNil(t, args) } diff --git a/pkg/agentic/epic.go b/pkg/agentic/epic.go index db605cf4..28e0afb6 100644 --- a/pkg/agentic/epic.go +++ b/pkg/agentic/epic.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/epic_example_test.go b/pkg/agentic/epic_example_test.go index ee03a11e..63d0b7c7 100644 --- a/pkg/agentic/epic_example_test.go +++ b/pkg/agentic/epic_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func ExampleEpicInput() { input := EpicInput{ diff --git a/pkg/agentic/epic_test.go b/pkg/agentic/epic_test.go index 5cf9a136..33364580 100644 --- a/pkg/agentic/epic_test.go +++ b/pkg/agentic/epic_test.go @@ -11,10 +11,8 @@ import ( "testing" "time" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/forge" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // mockForgeServer creates an httptest server that handles Forge API calls @@ -129,14 +127,14 @@ func newTestSubsystem(t *testing.T, srv *httptest.Server) *PrepSubsystem { t.Helper() s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), - forge: forge.NewForge(srv.URL, "test-token"), - forgeURL: srv.URL, - forgeToken: "test-token", - brainURL: srv.URL, - brainKey: "test-brain-key", - codePath: t.TempDir(), - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + forge: forge.NewForge(srv.URL, "test-token"), + forgeURL: srv.URL, + forgeToken: "test-token", + brainURL: srv.URL, + brainKey: "test-brain-key", + codePath: t.TempDir(), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } return s } @@ -148,11 +146,11 @@ func TestEpic_CreateIssue_Good_Success(t *testing.T) { s := newTestSubsystem(t, srv) child, err := s.createIssue(context.Background(), "core", "test-repo", "Fix the bug", "Description", []int64{1}) - require.NoError(t, err) - assert.Equal(t, 1, child.Number) - assert.Equal(t, "Fix the bug", child.Title) - assert.Contains(t, child.URL, "issues/1") - assert.Equal(t, int32(1), counter.Load()) + core.RequireNoError(t, err) + core.AssertEqual(t, 1, child.Number) + core.AssertEqual(t, "Fix the bug", child.Title) + core.AssertContains(t, child.URL, "issues/1") + core.AssertEqual(t, int32(1), counter.Load()) } func TestEpic_CreateIssue_Good_NoLabels(t *testing.T) { @@ -160,8 +158,8 @@ func TestEpic_CreateIssue_Good_NoLabels(t *testing.T) { s := newTestSubsystem(t, srv) child, err := s.createIssue(context.Background(), "core", "test-repo", "No labels task", "", nil) - require.NoError(t, err) - assert.Equal(t, "No labels task", child.Title) + core.RequireNoError(t, err) + core.AssertEqual(t, "No labels task", child.Title) } func TestEpic_CreateIssue_Good_WithBody(t *testing.T) { @@ -169,8 +167,8 @@ func TestEpic_CreateIssue_Good_WithBody(t *testing.T) { s := newTestSubsystem(t, srv) child, err := s.createIssue(context.Background(), "core", "test-repo", "Task with body", "Detailed description", []int64{1, 2}) - require.NoError(t, err) - assert.NotZero(t, child.Number) + core.RequireNoError(t, err) + assertNotZero(t, child.Number) } func TestEpic_CreateIssue_Bad_ServerDown(t *testing.T) { @@ -179,14 +177,14 @@ func TestEpic_CreateIssue_Bad_ServerDown(t *testing.T) { s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), - forgeURL: srv.URL, - forgeToken: "test-token", - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + forgeURL: srv.URL, + forgeToken: "test-token", + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } _, err := s.createIssue(context.Background(), "core", "test-repo", "Title", "", nil) - assert.Error(t, err) + core.AssertError(t, err) } func TestEpic_CreateIssue_Bad_Non201Response(t *testing.T) { @@ -197,14 +195,14 @@ func TestEpic_CreateIssue_Bad_Non201Response(t *testing.T) { s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), - forgeURL: srv.URL, - forgeToken: "test-token", - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + forgeURL: srv.URL, + forgeToken: "test-token", + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } _, err := s.createIssue(context.Background(), "core", "test-repo", "Title", "", nil) - assert.Error(t, err) + core.AssertError(t, err) } // --- resolveLabelIDs --- @@ -214,9 +212,9 @@ func TestEpic_ResolveLabelIDs_Good_ExistingLabels(t *testing.T) { s := newTestSubsystem(t, srv) ids := s.resolveLabelIDs(context.Background(), "core", "test-repo", []string{"agentic", "bug"}) - assert.Len(t, ids, 2) - assert.Contains(t, ids, int64(1)) - assert.Contains(t, ids, int64(2)) + core.AssertLen(t, ids, 2) + core.AssertContains(t, ids, int64(1)) + core.AssertContains(t, ids, int64(2)) } func TestEpic_ResolveLabelIDs_Good_NewLabel(t *testing.T) { @@ -225,7 +223,7 @@ func TestEpic_ResolveLabelIDs_Good_NewLabel(t *testing.T) { // "new-label" doesn't exist in mock, so it will be created ids := s.resolveLabelIDs(context.Background(), "core", "test-repo", []string{"new-label"}) - assert.NotEmpty(t, ids) + core.AssertNotEmpty(t, ids) } func TestEpic_ResolveLabelIDs_Good_EmptyNames(t *testing.T) { @@ -233,7 +231,7 @@ func TestEpic_ResolveLabelIDs_Good_EmptyNames(t *testing.T) { s := newTestSubsystem(t, srv) ids := s.resolveLabelIDs(context.Background(), "core", "test-repo", nil) - assert.Nil(t, ids) + core.AssertNil(t, ids) } func TestEpic_ResolveLabelIDs_Bad_ServerError(t *testing.T) { @@ -244,14 +242,14 @@ func TestEpic_ResolveLabelIDs_Bad_ServerError(t *testing.T) { s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), - forgeURL: srv.URL, - forgeToken: "test-token", - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + forgeURL: srv.URL, + forgeToken: "test-token", + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } ids := s.resolveLabelIDs(context.Background(), "core", "test-repo", []string{"agentic"}) - assert.Nil(t, ids) + core.AssertNil(t, ids) } // --- createLabel --- @@ -261,7 +259,7 @@ func TestEpic_CreateLabel_Good_Known(t *testing.T) { s := newTestSubsystem(t, srv) id := s.createLabel(context.Background(), "core", "test-repo", "agentic") - assert.NotZero(t, id) + assertNotZero(t, id) } func TestEpic_CreateLabel_Good_Unknown(t *testing.T) { @@ -270,7 +268,7 @@ func TestEpic_CreateLabel_Good_Unknown(t *testing.T) { // Unknown label uses default colour id := s.createLabel(context.Background(), "core", "test-repo", "custom-label") - assert.NotZero(t, id) + assertNotZero(t, id) } func TestEpic_CreateLabel_Bad_ServerDown(t *testing.T) { @@ -279,14 +277,14 @@ func TestEpic_CreateLabel_Bad_ServerDown(t *testing.T) { s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), - forgeURL: srv.URL, - forgeToken: "test-token", - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + forgeURL: srv.URL, + forgeToken: "test-token", + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } id := s.createLabel(context.Background(), "core", "test-repo", "agentic") - assert.Zero(t, id) + assertZero(t, id) } // --- createEpic (validation only, not full dispatch) --- @@ -299,8 +297,8 @@ func TestEpic_CreateEpic_Bad_NoTitle(t *testing.T) { Repo: "test-repo", Tasks: []string{"Task 1"}, }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "title is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "title is required") } func TestEpic_CreateEpic_Bad_NoTasks(t *testing.T) { @@ -311,16 +309,16 @@ func TestEpic_CreateEpic_Bad_NoTasks(t *testing.T) { Repo: "test-repo", Title: "Epic Title", }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "at least one task") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "at least one task") } func TestEpic_CreateEpic_Bad_NoToken(t *testing.T) { s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), - forgeToken: "", - backoff: make(map[string]time.Time), - failCount: make(map[string]int), + forgeToken: "", + backoff: make(map[string]time.Time), + failCount: make(map[string]int), } _, _, err := s.createEpic(context.Background(), nil, EpicInput{ @@ -328,8 +326,8 @@ func TestEpic_CreateEpic_Bad_NoToken(t *testing.T) { Title: "Epic", Tasks: []string{"Task"}, }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "no Forge token") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "no Forge token") } func TestEpic_CreateEpic_Good_WithTasks(t *testing.T) { @@ -341,14 +339,14 @@ func TestEpic_CreateEpic_Good_WithTasks(t *testing.T) { Title: "Test Epic", Tasks: []string{"Task 1", "Task 2"}, }) - require.NoError(t, err) - assert.True(t, out.Success) - assert.NotZero(t, out.EpicNumber) - assert.Len(t, out.Children, 2) - assert.Equal(t, "Task 1", out.Children[0].Title) - assert.Equal(t, "Task 2", out.Children[1].Title) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + assertNotZero(t, out.EpicNumber) + core.AssertLen(t, out.Children, 2) + core.AssertEqual(t, "Task 1", out.Children[0].Title) + core.AssertEqual(t, "Task 2", out.Children[1].Title) // 2 children + 1 epic = 3 issues - assert.Equal(t, int32(3), counter.Load()) + core.AssertEqual(t, int32(3), counter.Load()) } func TestEpic_CreateEpic_Good_WithLabels(t *testing.T) { @@ -361,8 +359,8 @@ func TestEpic_CreateEpic_Good_WithLabels(t *testing.T) { Tasks: []string{"Do it"}, Labels: []string{"bug"}, }) - require.NoError(t, err) - assert.True(t, out.Success) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) } func TestEpic_CreateEpic_Good_AgenticLabelAutoAdded(t *testing.T) { @@ -375,8 +373,8 @@ func TestEpic_CreateEpic_Good_AgenticLabelAutoAdded(t *testing.T) { Title: "Auto-labelled", Tasks: []string{"Task"}, }) - require.NoError(t, err) - assert.True(t, out.Success) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) } func TestEpic_CreateEpic_Good_AgenticLabelNotDuplicated(t *testing.T) { @@ -390,8 +388,8 @@ func TestEpic_CreateEpic_Good_AgenticLabelNotDuplicated(t *testing.T) { Tasks: []string{"Task"}, Labels: []string{"agentic"}, }) - require.NoError(t, err) - assert.True(t, out.Success) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) } // --- Ugly tests --- @@ -410,9 +408,9 @@ func TestEpic_CreateEpic_Ugly(t *testing.T) { Body: longBody, Tasks: []string{"Task 1"}, }) - require.NoError(t, err) - assert.True(t, out.Success) - assert.NotZero(t, out.EpicNumber) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + assertNotZero(t, out.EpicNumber) } func TestEpic_CreateIssue_Ugly(t *testing.T) { @@ -422,9 +420,9 @@ func TestEpic_CreateIssue_Ugly(t *testing.T) { htmlBody := "

Issue

This has bold and

" child, err := s.createIssue(context.Background(), "core", "test-repo", "HTML Issue", htmlBody, []int64{1}) - require.NoError(t, err) - assert.Equal(t, "HTML Issue", child.Title) - assert.NotZero(t, child.Number) + core.RequireNoError(t, err) + core.AssertEqual(t, "HTML Issue", child.Title) + assertNotZero(t, child.Number) } func TestEpic_ResolveLabelIDs_Ugly(t *testing.T) { @@ -434,7 +432,7 @@ func TestEpic_ResolveLabelIDs_Ugly(t *testing.T) { ids := s.resolveLabelIDs(context.Background(), "core", "test-repo", []string{"bug/fix", "feature:new", "label with spaces"}) // These will all be created as new labels since they don't match existing ones - assert.NotNil(t, ids) + core.AssertNotNil(t, ids) } func TestEpic_CreateLabel_Ugly(t *testing.T) { @@ -443,5 +441,5 @@ func TestEpic_CreateLabel_Ugly(t *testing.T) { s := newTestSubsystem(t, srv) id := s.createLabel(context.Background(), "core", "test-repo", "\u00e9nhancement-\u00fc\u00f1ic\u00f6de") - assert.NotZero(t, id) + assertNotZero(t, id) } diff --git a/pkg/agentic/events.go b/pkg/agentic/events.go index 99ee36b5..890385de 100644 --- a/pkg/agentic/events.go +++ b/pkg/agentic/events.go @@ -5,7 +5,7 @@ package agentic import ( "time" - core "dappco.re/go/core" + core "dappco.re/go" ) // event := agentic.CompletionEvent{Type: "agent_completed", Agent: "codex", Workspace: "go-io-123", Status: "completed"} diff --git a/pkg/agentic/events_example_test.go b/pkg/agentic/events_example_test.go index fad4b9f7..88f21595 100644 --- a/pkg/agentic/events_example_test.go +++ b/pkg/agentic/events_example_test.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func Example_emitStartEvent() { diff --git a/pkg/agentic/events_test.go b/pkg/agentic/events_test.go index 5de296a1..1d4ae9fd 100644 --- a/pkg/agentic/events_test.go +++ b/pkg/agentic/events_test.go @@ -5,8 +5,7 @@ package agentic import ( "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" + core "dappco.re/go" ) func TestEvents_EmitEvent_Good(t *testing.T) { @@ -14,20 +13,20 @@ func TestEvents_EmitEvent_Good(t *testing.T) { setTestWorkspace(t, root) fs.EnsureDir(core.JoinPath(root, "workspace")) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitStartEvent("codex", "ws-1") }) } func TestEvents_EmitEvent_Bad_NoWorkspace(t *testing.T) { setTestWorkspace(t, "/nonexistent") - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitCompletionEvent("codex", "ws-1", "completed") }) } func TestEvents_EmitEvent_Ugly_AllEmpty(t *testing.T) { - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitEvent("", "", "", "") }) } diff --git a/pkg/agentic/fetch_loop.go b/pkg/agentic/fetch_loop.go index 8543e359..872caae0 100644 --- a/pkg/agentic/fetch_loop.go +++ b/pkg/agentic/fetch_loop.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" "gopkg.in/yaml.v3" ) @@ -17,6 +17,18 @@ type fetchRepoRef struct { Repo string } +var fetchLoopDefaultBranchFunc = func(s *PrepSubsystem, repoDir string) string { + return s.DefaultBranch(repoDir) +} + +var fetchLoopRunFetchFunc = func(s *PrepSubsystem, ctx context.Context, repoDir, branch string) core.Result { + args := []string{"fetch", "origin"} + if branch != "" { + args = append(args, branch) + } + return s.Core().Process().RunIn(ctx, repoDir, "git", args...) +} + // go s.runFetchLoop(ctx, 5*time.Minute) func (s *PrepSubsystem) runFetchLoop(ctx context.Context, interval time.Duration) { if s == nil || s.ServiceRuntime == nil || ctx == nil || interval <= 0 { @@ -90,13 +102,8 @@ func (s *PrepSubsystem) fetchRegisteredRepos(ctx context.Context) { } seen[repoDir] = true - branch := s.DefaultBranch(repoDir) - args := []string{"git", "fetch", "origin"} - if branch != "" { - args = append(args, branch) - } - - result := s.Core().Process().RunIn(ctx, repoDir, args...) + branch := fetchLoopDefaultBranchFunc(s, repoDir) + result := fetchLoopRunFetchFunc(s, ctx, repoDir, branch) if !result.OK { core.Warn("agentic fetch loop failed", "repo", name, "branch", branch, "reason", result.Value) continue diff --git a/pkg/agentic/fetch_loop_test.go b/pkg/agentic/fetch_loop_test.go index c84a6553..9d58d1b8 100644 --- a/pkg/agentic/fetch_loop_test.go +++ b/pkg/agentic/fetch_loop_test.go @@ -4,12 +4,11 @@ package agentic import ( "context" + "sync" "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestFetchLoop_RunFetchLoop_Good_TicksAtConfiguredInterval(t *testing.T) { @@ -17,10 +16,8 @@ func TestFetchLoop_RunFetchLoop_Good_TicksAtConfiguredInterval(t *testing.T) { setTestWorkspace(t, root) codePath := t.TempDir() - logPath := core.JoinPath(t.TempDir(), "git.log") - fetchLoopWriteGitScript(t, logPath, "bad-repo") fetchLoopCreateRepo(t, codePath, "core", "good-repo") - require.True(t, fs.Write(core.JoinPath(root, "agents.yaml"), core.Concat( + core.RequireTrue(t, fs.Write(core.JoinPath(root, "agents.yaml"), core.Concat( "version: 1\n", "dispatch:\n", " fetch_interval: 25ms\n", @@ -30,7 +27,8 @@ func TestFetchLoop_RunFetchLoop_Good_TicksAtConfiguredInterval(t *testing.T) { subsystem := fetchLoopTestPrep(codePath) interval := subsystem.fetchLoopInterval() - assert.Equal(t, 25*time.Millisecond, interval) + core.AssertEqual(t, 25*time.Millisecond, interval) + state := stubFetchLoop(t, "") ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) @@ -40,9 +38,9 @@ func TestFetchLoop_RunFetchLoop_Good_TicksAtConfiguredInterval(t *testing.T) { }() time.Sleep(10 * time.Millisecond) - assert.Equal(t, 0, fetchLoopLogCount(logPath, "good-repo", "fetch origin dev")) + core.AssertEqual(t, 0, state.count("good-repo")) - fetchLoopWaitForCount(t, logPath, "good-repo", "fetch origin dev", 2, 250*time.Millisecond) + waitForFetchLoopCalls(t, state, "good-repo", 2, 250*time.Millisecond) cancel() fetchLoopWaitForDone(t, done) @@ -53,11 +51,9 @@ func TestFetchLoop_RunFetchLoop_Bad_SurvivesFailingFetch(t *testing.T) { setTestWorkspace(t, root) codePath := t.TempDir() - logPath := core.JoinPath(t.TempDir(), "git.log") - fetchLoopWriteGitScript(t, logPath, "bad-repo") fetchLoopCreateRepo(t, codePath, "core", "good-repo") fetchLoopCreateRepo(t, codePath, "core", "bad-repo") - require.True(t, fs.Write(core.JoinPath(root, "agents.yaml"), core.Concat( + core.RequireTrue(t, fs.Write(core.JoinPath(root, "agents.yaml"), core.Concat( "version: 1\n", "dispatch:\n", " fetch_interval: 15ms\n", @@ -67,6 +63,7 @@ func TestFetchLoop_RunFetchLoop_Bad_SurvivesFailingFetch(t *testing.T) { )).OK) subsystem := fetchLoopTestPrep(codePath) + state := stubFetchLoop(t, "bad-repo") ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { @@ -74,8 +71,8 @@ func TestFetchLoop_RunFetchLoop_Bad_SurvivesFailingFetch(t *testing.T) { close(done) }() - fetchLoopWaitForCount(t, logPath, "bad-repo", "fetch origin dev", 1, 250*time.Millisecond) - fetchLoopWaitForCount(t, logPath, "good-repo", "fetch origin dev", 2, 250*time.Millisecond) + waitForFetchLoopCalls(t, state, "bad-repo", 1, 250*time.Millisecond) + waitForFetchLoopCalls(t, state, "good-repo", 2, 250*time.Millisecond) cancel() fetchLoopWaitForDone(t, done) @@ -86,10 +83,8 @@ func TestFetchLoop_RunFetchLoop_Ugly_StopsOnContextCancel(t *testing.T) { setTestWorkspace(t, root) codePath := t.TempDir() - logPath := core.JoinPath(t.TempDir(), "git.log") - fetchLoopWriteGitScript(t, logPath, "bad-repo") fetchLoopCreateRepo(t, codePath, "core", "good-repo") - require.True(t, fs.Write(core.JoinPath(root, "agents.yaml"), core.Concat( + core.RequireTrue(t, fs.Write(core.JoinPath(root, "agents.yaml"), core.Concat( "version: 1\n", "dispatch:\n", " fetch_interval: 15ms\n", @@ -98,6 +93,7 @@ func TestFetchLoop_RunFetchLoop_Ugly_StopsOnContextCancel(t *testing.T) { )).OK) subsystem := fetchLoopTestPrep(codePath) + state := stubFetchLoop(t, "") ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { @@ -105,14 +101,14 @@ func TestFetchLoop_RunFetchLoop_Ugly_StopsOnContextCancel(t *testing.T) { close(done) }() - fetchLoopWaitForCount(t, logPath, "good-repo", "fetch origin dev", 1, 250*time.Millisecond) + waitForFetchLoopCalls(t, state, "good-repo", 1, 250*time.Millisecond) cancel() fetchLoopWaitForDone(t, done) - countAfterCancel := fetchLoopLogCount(logPath, "good-repo", "fetch origin dev") + countAfterCancel := state.count("good-repo") time.Sleep(50 * time.Millisecond) - assert.Equal(t, countAfterCancel, fetchLoopLogCount(logPath, "good-repo", "fetch origin dev")) + core.AssertEqual(t, countAfterCancel, state.count("good-repo")) } func fetchLoopTestPrep(codePath string) *PrepSubsystem { @@ -124,6 +120,67 @@ func fetchLoopTestPrep(codePath string) *PrepSubsystem { } } +type fetchLoopCallState struct { + mu sync.Mutex + calls map[string]int + fail string +} + +func stubFetchLoop(t *testing.T, failRepo string) *fetchLoopCallState { + t.Helper() + + state := &fetchLoopCallState{ + calls: map[string]int{}, + fail: failRepo, + } + previousBranch := fetchLoopDefaultBranchFunc + previousFetch := fetchLoopRunFetchFunc + + fetchLoopDefaultBranchFunc = func(_ *PrepSubsystem, _ string) string { + return "dev" + } + fetchLoopRunFetchFunc = func(_ *PrepSubsystem, ctx context.Context, repoDir, _ string) core.Result { + repo := core.PathBase(repoDir) + state.mu.Lock() + state.calls[repo]++ + state.mu.Unlock() + if err := ctx.Err(); err != nil { + return core.Fail(core.E("fetchLoopRunFetch", "context canceled", err)) + } + if repo == state.fail { + return core.Fail(core.E("fetchLoopRunFetch", "fetch failed", nil)) + } + return core.Ok(nil) + } + + t.Cleanup(func() { + fetchLoopDefaultBranchFunc = previousBranch + fetchLoopRunFetchFunc = previousFetch + }) + + return state +} + +func (s *fetchLoopCallState) count(repo string) int { + s.mu.Lock() + defer s.mu.Unlock() + return s.calls[repo] +} + +func waitForFetchLoopCalls(t *testing.T, state *fetchLoopCallState, repo string, want int, timeout time.Duration) { + t.Helper() + + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + if state.count(repo) >= want { + return + } + time.Sleep(5 * time.Millisecond) + } + + core.AssertGreaterOrEqual(t, state.count(repo), want) +} + func fetchLoopWriteGitScript(t *testing.T, logPath, badRepo string) { t.Helper() @@ -131,7 +188,7 @@ func fetchLoopWriteGitScript(t *testing.T, logPath, badRepo string) { gitPath := core.JoinPath(binDir, "git") script := core.Concat( "#!/bin/sh\n", - "repo=$(basename \"$PWD\")\n", + "repo=$(basename \"$(pwd)\")\n", "printf '%s|%s\\n' \"$repo\" \"$*\" >> ", logPath, "\n", "if [ \"$1\" = \"symbolic-ref\" ]; then\n", " printf 'origin/dev\\n'\n", @@ -142,15 +199,15 @@ func fetchLoopWriteGitScript(t *testing.T, logPath, badRepo string) { "fi\n", "exit 0\n", ) - require.True(t, fs.Write(gitPath, script).OK) - require.True(t, testCore.Process().RunIn(context.Background(), binDir, "chmod", "+x", gitPath).OK) + core.RequireTrue(t, fs.Write(gitPath, script).OK) + core.RequireTrue(t, testCore.Process().RunIn(context.Background(), binDir, "chmod", "+x", gitPath).OK) t.Setenv("PATH", core.Concat(binDir, ":", core.Env("PATH"))) } func fetchLoopCreateRepo(t *testing.T, codePath, org, repo string) { t.Helper() repoDir := core.JoinPath(codePath, org, repo) - require.True(t, fs.EnsureDir(core.JoinPath(repoDir, ".git")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(repoDir, ".git")).OK) } func fetchLoopWaitForCount(t *testing.T, logPath, repo, snippet string, want int, timeout time.Duration) { @@ -164,7 +221,7 @@ func fetchLoopWaitForCount(t *testing.T, logPath, repo, snippet string, want int time.Sleep(5 * time.Millisecond) } - require.GreaterOrEqual(t, fetchLoopLogCount(logPath, repo, snippet), want) + core.AssertGreaterOrEqual(t, fetchLoopLogCount(logPath, repo, snippet), want) } func fetchLoopWaitForDone(t *testing.T, done <-chan struct{}) { diff --git a/pkg/agentic/fleet_connect.go b/pkg/agentic/fleet_connect.go index eaa36579..0e295d8d 100644 --- a/pkg/agentic/fleet_connect.go +++ b/pkg/agentic/fleet_connect.go @@ -9,7 +9,7 @@ import ( "sync" "time" - core "dappco.re/go/core" + core "dappco.re/go" ) var fleetBackoffSchedule = []time.Duration{ @@ -161,7 +161,9 @@ func (s *PrepSubsystem) startFleetPollFallback(ctx context.Context, config fleet pollingDone := make(chan struct{}) go func() { defer close(pollingDone) - _ = s.runFleetPollFallback(pollingContext, config) + if result := s.runFleetPollFallback(pollingContext, config); !result.OK { + core.Warn("fleet poll fallback exited", "reason", result.Value) + } }() return cancelPolling, pollingDone } @@ -602,7 +604,9 @@ func resetFleetRuntimeState() { fleetRuntimeState.mu.Lock() fleetRuntimeState.snapshot = fleetRuntimeSnapshot{State: "offline"} fleetRuntimeState.mu.Unlock() - _ = fs.Delete(fleetStatusSnapshotPath()) + if deleteResult := fs.Delete(fleetStatusSnapshotPath()); !deleteResult.OK && fs.Exists(fleetStatusSnapshotPath()) { + core.Warn("agentic: failed to delete fleet status snapshot", "path", fleetStatusSnapshotPath(), "reason", deleteResult.Value) + } } func fleetSnapshotEmpty(snapshot fleetRuntimeSnapshot) bool { @@ -624,7 +628,9 @@ func persistFleetRuntimeSnapshot(snapshot fleetRuntimeSnapshot) { if ensureResult := fs.EnsureDir(core.PathDir(fleetStatusSnapshotPath())); !ensureResult.OK { return } - _ = fs.WriteMode(fleetStatusSnapshotPath(), core.JSONMarshalString(snapshot), 0644) + if writeResult := fs.WriteMode(fleetStatusSnapshotPath(), core.JSONMarshalString(snapshot), 0644); !writeResult.OK { + core.Warn("agentic: failed to write fleet status snapshot", "path", fleetStatusSnapshotPath(), "reason", writeResult.Value) + } } func loadFleetRuntimeSnapshot() fleetRuntimeSnapshot { diff --git a/pkg/agentic/fleet_connect_test.go b/pkg/agentic/fleet_connect_test.go index 1edfc2fb..4f54b719 100644 --- a/pkg/agentic/fleet_connect_test.go +++ b/pkg/agentic/fleet_connect_test.go @@ -9,12 +9,10 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) -func TestConnect_BackoffOnFailure(t *testing.T) { +func TestBackoffOnFailure_PrepSubsystem_Connect_Good(t *testing.T) { t.Setenv("CORE_HOME", t.TempDir()) resetFleetRuntimeState() @@ -31,7 +29,7 @@ func TestConnect_BackoffOnFailure(t *testing.T) { }) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/fleet/events", r.URL.Path) + core.AssertEqual(t, "/v1/fleet/events", r.URL.Path) w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte(`{"error":"stream unavailable"}`)) })) @@ -61,8 +59,8 @@ func TestConnect_BackoffOnFailure(t *testing.T) { } result := subsystem.Connect(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) - require.True(t, result.OK) - assert.Equal(t, []time.Duration{ + core.RequireTrue(t, result.OK) + core.AssertEqual(t, []time.Duration{ time.Millisecond, 2 * time.Millisecond, 4 * time.Millisecond, @@ -72,35 +70,35 @@ func TestConnect_BackoffOnFailure(t *testing.T) { }, durations) } -func TestPollFallback_Good(t *testing.T) { +func TestPollFallback_PrepSubsystem_PollFallback_Good(t *testing.T) { t.Setenv("CORE_HOME", t.TempDir()) resetFleetRuntimeState() t.Cleanup(resetFleetRuntimeState) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/fleet/task/next", r.URL.Path) - require.Equal(t, "agent_id=charon", r.URL.RawQuery) - require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/fleet/task/next", r.URL.Path) + core.AssertEqual(t, "agent_id=charon", r.URL.RawQuery) + core.AssertEqual(t, "Bearer secret-token", r.Header.Get("Authorization")) _, _ = w.Write([]byte(`{"data":{"task":{"id":7,"repo":"core/go-io","branch":"dev","task":"Fix tests","status":"assigned"}}}`)) })) defer server.Close() subsystem := testPrepWithPlatformServer(t, server, "secret-token") result := subsystem.PollFallback(context.Background(), core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) task, ok := result.Value.(*FleetTask) - require.True(t, ok) - require.NotNil(t, task) - assert.Equal(t, 7, task.ID) - assert.Equal(t, "core/go-io", task.Repo) + core.RequireTrue(t, ok) + core.AssertNotNil(t, task) + core.AssertEqual(t, 7, task.ID) + core.AssertEqual(t, "core/go-io", task.Repo) snapshot := fleetRuntimeSnapshotValue() - assert.Equal(t, "polling", snapshot.State) - assert.Equal(t, 7, snapshot.LastTask.ID) + core.AssertEqual(t, "polling", snapshot.State) + core.AssertEqual(t, 7, snapshot.LastTask.ID) } -func TestHeartbeat_Good(t *testing.T) { +func TestHeartbeat_PrepSubsystem_Heartbeat_Good(t *testing.T) { t.Setenv("CORE_HOME", t.TempDir()) resetFleetRuntimeState() @@ -114,18 +112,18 @@ func TestHeartbeat_Good(t *testing.T) { requests := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/fleet/heartbeat", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) - require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/fleet/heartbeat", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "Bearer secret-token", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "charon", payload["agent_id"]) - assert.Equal(t, "online", payload["status"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "charon", payload["agent_id"]) + core.AssertEqual(t, "online", payload["status"]) requests++ _, _ = w.Write([]byte(`{"data":{"ok":true}}`)) @@ -145,7 +143,7 @@ func TestHeartbeat_Good(t *testing.T) { } result := subsystem.Heartbeat(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"})) - require.True(t, result.OK) - assert.Equal(t, 1, requests) - assert.NotEmpty(t, fleetRuntimeSnapshotValue().LastHeartbeatAt) + core.RequireTrue(t, result.OK) + core.AssertEqual(t, 1, requests) + core.AssertNotEmpty(t, fleetRuntimeSnapshotValue().LastHeartbeatAt) } diff --git a/pkg/agentic/fleet_login.go b/pkg/agentic/fleet_login.go index 6ed82cad..f69ba585 100644 --- a/pkg/agentic/fleet_login.go +++ b/pkg/agentic/fleet_login.go @@ -6,7 +6,7 @@ import ( "context" "net/http" - core "dappco.re/go/core" + core "dappco.re/go" ) type fleetLoginOutput struct { diff --git a/pkg/agentic/fleet_login_test.go b/pkg/agentic/fleet_login_test.go index 0dd08df0..645745f4 100644 --- a/pkg/agentic/fleet_login_test.go +++ b/pkg/agentic/fleet_login_test.go @@ -8,24 +8,22 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestLogin_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/device/pair", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) - require.Equal(t, "", r.Header.Get("Authorization")) + core.AssertEqual(t, "/v1/device/pair", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) + core.AssertEqual(t, "", r.Header.Get("Authorization")) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "123456", payload["code"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "123456", payload["code"]) _, _ = w.Write([]byte(`{"agent_api_key":"ak_live_test","agent_id":"charon","expires_at":"2027-01-01T00:00:00Z"}`)) })) @@ -37,22 +35,22 @@ func TestLogin_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "") output := captureStdout(t, func() { result := subsystem.cmdFleetLogin(core.NewOptions(core.Option{Key: "_arg", Value: "123456"})) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "logged in") - assert.Contains(t, output, "agent: charon") - assert.Contains(t, output, "saved to:") + core.AssertContains(t, output, "logged in") + core.AssertContains(t, output, "agent: charon") + core.AssertContains(t, output, "saved to:") keyPath := core.JoinPath(homeDir, ".core", "agent.key") readResult := fs.Read(keyPath) - require.True(t, readResult.OK) - assert.Equal(t, "ak_live_test", core.Trim(readResult.Value.(string))) + core.RequireTrue(t, readResult.OK) + core.AssertEqual(t, "ak_live_test", core.Trim(readResult.Value.(string))) statResult := fs.Stat(keyPath) - require.True(t, statResult.OK) + core.RequireTrue(t, statResult.OK) info := statResult.Value.(iofs.FileInfo) - assert.Equal(t, iofs.FileMode(0600), info.Mode().Perm()) + core.AssertEqual(t, iofs.FileMode(0600), info.Mode().Perm()) } func TestLogin_Bad(t *testing.T) { @@ -65,11 +63,11 @@ func TestLogin_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "") output := captureStdout(t, func() { result := subsystem.cmdFleetLogin(core.NewOptions(core.Option{Key: "_arg", Value: "123456"})) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) }) - assert.Contains(t, output, "error:") - assert.Contains(t, output, "invalid pairing code") + core.AssertContains(t, output, "error:") + core.AssertContains(t, output, "invalid pairing code") } func TestLogin_Ugly(t *testing.T) { @@ -79,9 +77,9 @@ func TestLogin_Ugly(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "") output := captureStdout(t, func() { result := subsystem.cmdFleetLogin(core.NewOptions(core.Option{Key: "_arg", Value: "12ab"})) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) }) - assert.Contains(t, output, "usage: core-agent login <6-digit-code>") - assert.False(t, fs.Exists(core.JoinPath(homeDir, ".core", "agent.key"))) + core.AssertContains(t, output, "usage: core-agent login <6-digit-code>") + core.AssertFalse(t, fs.Exists(core.JoinPath(homeDir, ".core", "agent.key"))) } diff --git a/pkg/agentic/fleet_mode.go b/pkg/agentic/fleet_mode.go index 52854d2f..1905e824 100644 --- a/pkg/agentic/fleet_mode.go +++ b/pkg/agentic/fleet_mode.go @@ -6,7 +6,7 @@ import ( "context" "net/http" - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) registerFleetCommands() { diff --git a/pkg/agentic/fleet_mode_test.go b/pkg/agentic/fleet_mode_test.go index 805a662a..70a446b9 100644 --- a/pkg/agentic/fleet_mode_test.go +++ b/pkg/agentic/fleet_mode_test.go @@ -8,9 +8,7 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestFleetMode_RegisterFleetCommands_Good(t *testing.T) { @@ -20,10 +18,10 @@ func TestFleetMode_RegisterFleetCommands_Good(t *testing.T) { subsystem.registerFleetCommands() - assert.Contains(t, c.Commands(), "login") - assert.Contains(t, c.Commands(), "fleet") - assert.Contains(t, c.Commands(), "fleet/nodes") - assert.Contains(t, c.Commands(), "fleet/status") + core.AssertContains(t, c.Commands(), "login") + core.AssertContains(t, c.Commands(), "fleet") + core.AssertContains(t, c.Commands(), "fleet/nodes") + core.AssertContains(t, c.Commands(), "fleet/status") } func TestFleetMode_CmdFleetNodes_Good(t *testing.T) { @@ -36,11 +34,11 @@ func TestFleetMode_CmdFleetNodes_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, server, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdFleetNodesCommand(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "charon") - assert.Contains(t, output, "total: 1") + core.AssertContains(t, output, "charon") + core.AssertContains(t, output, "total: 1") } func TestFleetMode_CmdFleetStatus_Good(t *testing.T) { @@ -55,11 +53,11 @@ func TestFleetMode_CmdFleetStatus_Good(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "secret-token") output := captureStdout(t, func() { result := subsystem.cmdFleetStatus(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "state: connected") - assert.Contains(t, output, "last task: #9 core/go-io assigned Fix tests") + core.AssertContains(t, output, "state: connected") + core.AssertContains(t, output, "last task: #9 core/go-io assigned Fix tests") } func TestFleetMode_ListFleetNodes_Bad_Unreachable(t *testing.T) { @@ -68,5 +66,5 @@ func TestFleetMode_ListFleetNodes_Bad_Unreachable(t *testing.T) { result := subsystem.listFleetNodes(context.Background(), core.NewOptions( core.Option{Key: "api", Value: "http://127.0.0.1:1"}, )) - assert.False(t, result.OK) + core.AssertFalse(t, result.OK) } diff --git a/pkg/agentic/flow.go b/pkg/agentic/flow.go index 4982a391..c2f3ffa2 100644 --- a/pkg/agentic/flow.go +++ b/pkg/agentic/flow.go @@ -6,7 +6,7 @@ import ( "io" "os" - core "dappco.re/go/core" + core "dappco.re/go" ) // FlowRunStepOutput captures the per-step result of a flow execution: the diff --git a/pkg/agentic/handlers.go b/pkg/agentic/handlers.go index e898482f..cf670f6f 100644 --- a/pkg/agentic/handlers.go +++ b/pkg/agentic/handlers.go @@ -5,8 +5,8 @@ package agentic import ( "context" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" ) // c := core.New(core.WithService(agentic.ProcessRegister)) diff --git a/pkg/agentic/handlers_example_test.go b/pkg/agentic/handlers_example_test.go index d0fb6591..90758f8d 100644 --- a/pkg/agentic/handlers_example_test.go +++ b/pkg/agentic/handlers_example_test.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func Example_resolveWorkspace() { diff --git a/pkg/agentic/handlers_test.go b/pkg/agentic/handlers_test.go index e7b3e652..143a1ee5 100644 --- a/pkg/agentic/handlers_test.go +++ b/pkg/agentic/handlers_test.go @@ -8,10 +8,8 @@ import ( "testing" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // newCoreForHandlerTests creates a Core with PrepSubsystem registered via @@ -41,10 +39,10 @@ func newCoreForHandlerTests(t *testing.T) (*core.Core, *PrepSubsystem) { // --- HandleIPCEvents --- -func TestHandlers_HandleIPCEvents_Good(t *testing.T) { +func TestHandlers_PrepSubsystem_HandleIPCEvents_Good(t *testing.T) { c, _ := newCoreForHandlerTests(t) // HandleIPCEvents was auto-registered — Core should not panic on ACTION - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { c.ACTION(messages.AgentCompleted{Workspace: "nonexistent", Repo: "test", Status: "completed"}) }) } @@ -68,7 +66,7 @@ func TestHandlers_PokeOnCompletion_Good(t *testing.T) { Workspace: "ws-test", Repo: "go-io", Status: "completed", }) - require.Eventually(t, func() bool { return len(poked) == 1 }, time.Second, 10*time.Millisecond) + requireEventually(t, func() bool { return len(poked) == 1 }, time.Second, 10*time.Millisecond) } func TestHandlers_IngestOnCompletion_Good(t *testing.T) { @@ -100,7 +98,7 @@ func TestHandlers_IgnoresNonCompleted_Good(t *testing.T) { c, _ := newCoreForHandlerTests(t) // Non-completed status — ingest still runs (it handles all completions) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { c.ACTION(messages.AgentCompleted{ Workspace: "nonexistent", Repo: "test", @@ -118,14 +116,14 @@ func TestHandlers_PokeQueue_Good(t *testing.T) { // Should call drainQueue without panic } -func TestHandlers_RegisterHandlers_Good_CompletionPipeline(t *testing.T) { +func TestCompletionPipeline_RegisterHandlers_Good(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) workspaceName := "core/go-io/task-5" workspaceDir := core.JoinPath(root, "workspace", "core", "go-io", "task-5") - require.True(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo")).OK) - require.NoError(t, writeStatus(workspaceDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo")).OK) + core.RequireNoError(t, writeStatus(workspaceDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Branch: "agent/fix-tests", @@ -194,7 +192,7 @@ func TestHandlers_RegisterHandlers_Good_CompletionPipeline(t *testing.T) { Status: "completed", }) - require.Eventually(t, func() bool { + requireEventually(t, func() bool { return seen("qa") && seen("auto-pr") && seen("verify") && seen("ingest") && seen("poke") }, time.Second, 10*time.Millisecond) } @@ -205,16 +203,16 @@ func TestHandlers_FindWorkspaceByPR_Good_MatchesPRNumber(t *testing.T) { firstWorkspace := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-1") secondWorkspace := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-2") - require.True(t, fs.EnsureDir(firstWorkspace).OK) - require.True(t, fs.EnsureDir(secondWorkspace).OK) + core.RequireTrue(t, fs.EnsureDir(firstWorkspace).OK) + core.RequireTrue(t, fs.EnsureDir(secondWorkspace).OK) - require.NoError(t, writeStatus(firstWorkspace, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(firstWorkspace, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Branch: "agent/first", PRURL: "https://forge.lthn.ai/core/go-io/pulls/12", })) - require.NoError(t, writeStatus(secondWorkspace, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(secondWorkspace, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Branch: "agent/second", @@ -222,7 +220,7 @@ func TestHandlers_FindWorkspaceByPR_Good_MatchesPRNumber(t *testing.T) { })) result := findWorkspaceByPRWithInfo("go-io", "", 13, "https://forge.lthn.ai/core/go-io/pulls/13") - assert.Equal(t, secondWorkspace, result) + core.AssertEqual(t, secondWorkspace, result) } func TestHandlers_IngestDisabled_Bad(t *testing.T) { @@ -258,7 +256,7 @@ func TestHandlers_ResolveWorkspace_Good(t *testing.T) { fs.EnsureDir(ws) result := resolveWorkspace("core/go-io/task-15") - assert.Equal(t, ws, result) + core.AssertEqual(t, ws, result) } func TestHandlers_ResolveWorkspace_Bad(t *testing.T) { @@ -266,7 +264,7 @@ func TestHandlers_ResolveWorkspace_Bad(t *testing.T) { setTestWorkspace(t, root) result := resolveWorkspace("nonexistent") - assert.Empty(t, result) + core.AssertEmpty(t, result) } func TestHandlers_FindWorkspaceByPR_Good(t *testing.T) { @@ -280,7 +278,7 @@ func TestHandlers_FindWorkspaceByPR_Good(t *testing.T) { fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(st)) result := findWorkspaceByPR("go-io", "agent/fix") - assert.Equal(t, ws, result) + core.AssertEqual(t, ws, result) } func TestHandlers_FindWorkspaceByPR_Ugly(t *testing.T) { @@ -295,7 +293,7 @@ func TestHandlers_FindWorkspaceByPR_Ugly(t *testing.T) { fs.Write(core.JoinPath(ws, "status.json"), core.JSONMarshalString(st)) result := findWorkspaceByPR("agent", "agent/tests") - assert.Equal(t, ws, result) + core.AssertEqual(t, ws, result) } // --- command registration --- @@ -309,7 +307,7 @@ func TestHandlers_Commandsforge_Good(t *testing.T) { backoff: make(map[string]time.Time), failCount: make(map[string]int), } - assert.NotPanics(t, func() { s.registerForgeCommands() }) + core.AssertNotPanics(t, func() { s.registerForgeCommands() }) } func TestHandlers_Commandsworkspace_Good(t *testing.T) { @@ -321,5 +319,5 @@ func TestHandlers_Commandsworkspace_Good(t *testing.T) { backoff: make(map[string]time.Time), failCount: make(map[string]int), } - assert.NotPanics(t, func() { s.registerWorkspaceCommands() }) + core.AssertNotPanics(t, func() { s.registerWorkspaceCommands() }) } diff --git a/pkg/agentic/ingest.go b/pkg/agentic/ingest.go index 55630e69..6e91d627 100644 --- a/pkg/agentic/ingest.go +++ b/pkg/agentic/ingest.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" ) func (s *PrepSubsystem) ingestFindings(workspaceDir string) { diff --git a/pkg/agentic/ingest_example_test.go b/pkg/agentic/ingest_example_test.go index 7c1a8ad8..33aae0d8 100644 --- a/pkg/agentic/ingest_example_test.go +++ b/pkg/agentic/ingest_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func Example_ingestWorkspaceRoot() { root := WorkspaceRoot() diff --git a/pkg/agentic/ingest_test.go b/pkg/agentic/ingest_test.go index 765ea0ac..01c99a2e 100644 --- a/pkg/agentic/ingest_test.go +++ b/pkg/agentic/ingest_test.go @@ -8,9 +8,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) // --- ingestFindings --- @@ -23,7 +21,7 @@ func TestIngest_IngestFindings_Good_WithFindings(t *testing.T) { issueCalled = true var body map[string]string core.JSONUnmarshalString(core.ReadAll(r.Body).Value.(string), &body) - assert.Contains(t, body["title"], "Scan findings") + core.AssertContains(t, body["title"], "Scan findings") w.WriteHeader(201) return } @@ -33,7 +31,7 @@ func TestIngest_IngestFindings_Good_WithFindings(t *testing.T) { // Create a workspace with status and log file wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Agent: "codex", @@ -45,14 +43,15 @@ func TestIngest_IngestFindings_Good_WithFindings(t *testing.T) { "- `pkg/core/service.go:100` has a missing error check\n" + "- `pkg/core/config.go:25` needs documentation\n" + "This is padding to get past the 100 char minimum length requirement for the log file content parsing." - require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) - require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK) // Set up HOME for the agent-api.key read home := t.TempDir() t.Setenv("HOME", home) - require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK) - require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-api-key").OK) + t.Setenv("CORE_HOME", home) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-api-key").OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -63,12 +62,12 @@ func TestIngest_IngestFindings_Good_WithFindings(t *testing.T) { } s.ingestFindings(wsDir) - assert.True(t, issueCalled, "should have created an issue via API") + core.AssertTrue(t, issueCalled, "should have created an issue via API") } func TestIngest_IngestFindings_Bad_NotCompleted(t *testing.T) { wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "running", Repo: "go-io", })) @@ -80,14 +79,14 @@ func TestIngest_IngestFindings_Bad_NotCompleted(t *testing.T) { } // Should return early — status is not "completed" - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } func TestIngest_IngestFindings_Bad_NoLogFile(t *testing.T) { wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", })) @@ -99,22 +98,22 @@ func TestIngest_IngestFindings_Bad_NoLogFile(t *testing.T) { } // Should return early — no log files - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } func TestIngest_IngestFindings_Bad_TooFewFindings(t *testing.T) { wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", })) // Only 1 finding (need >= 2 to ingest) logContent := "Found: `main.go:1` has an issue. This padding makes the content long enough to pass the 100 char minimum check." - require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) - require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -122,22 +121,22 @@ func TestIngest_IngestFindings_Bad_TooFewFindings(t *testing.T) { failCount: make(map[string]int), } - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } func TestIngest_IngestFindings_Bad_QuotaExhausted(t *testing.T) { wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", })) // Log contains quota error — should skip logContent := "QUOTA_EXHAUSTED: Rate limit exceeded. `main.go:1` `other.go:2` padding to ensure we pass length check and get past the threshold." - require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) - require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), logContent).OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -145,7 +144,7 @@ func TestIngest_IngestFindings_Bad_QuotaExhausted(t *testing.T) { failCount: make(map[string]int), } - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } @@ -159,21 +158,21 @@ func TestIngest_IngestFindings_Bad_NoStatusFile(t *testing.T) { failCount: make(map[string]int), } - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } func TestIngest_IngestFindings_Bad_ShortLogFile(t *testing.T) { wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", })) // Log content is less than 100 bytes — should skip - require.True(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) - require.True(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), "short").OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(wsDir, ".meta")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(wsDir, ".meta", "agent-codex.log"), "short").OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -181,7 +180,7 @@ func TestIngest_IngestFindings_Bad_ShortLogFile(t *testing.T) { failCount: make(map[string]int), } - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } @@ -192,16 +191,16 @@ func TestIngest_CreateIssueViaAPI_Good_Success(t *testing.T) { called := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true - assert.Equal(t, "POST", r.Method) - assert.Contains(t, r.URL.Path, "/v1/issues") + core.AssertEqual(t, "POST", r.Method) + core.AssertContains(t, r.URL.Path, "/v1/issues") // Auth header should be present (Bearer + some key) - assert.Contains(t, r.Header.Get("Authorization"), "Bearer ") + core.AssertContains(t, r.Header.Get("Authorization"), "Bearer ") var body map[string]string core.JSONUnmarshalString(core.ReadAll(r.Body).Value.(string), &body) - assert.Equal(t, "Test Issue", body["title"]) - assert.Equal(t, "bug", body["type"]) - assert.Equal(t, "high", body["priority"]) + core.AssertEqual(t, "Test Issue", body["title"]) + core.AssertEqual(t, "bug", body["type"]) + core.AssertEqual(t, "high", body["priority"]) w.WriteHeader(201) })) @@ -209,8 +208,9 @@ func TestIngest_CreateIssueViaAPI_Good_Success(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) - require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK) - require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK) + t.Setenv("CORE_HOME", home) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -221,7 +221,7 @@ func TestIngest_CreateIssueViaAPI_Good_Success(t *testing.T) { } s.createIssueViaAPI("Test Issue", "Description", "bug", "high") - assert.True(t, called) + core.AssertTrue(t, called) } func TestIngest_CreateIssueViaAPI_Bad_NoBrainKey(t *testing.T) { @@ -233,7 +233,7 @@ func TestIngest_CreateIssueViaAPI_Bad_NoBrainKey(t *testing.T) { } // Should return early without panic - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.createIssueViaAPI("Title", "Body", "task", "normal") }) } @@ -241,6 +241,7 @@ func TestIngest_CreateIssueViaAPI_Bad_NoBrainKey(t *testing.T) { func TestIngest_CreateIssueViaAPI_Bad_NoAPIKey(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) + t.Setenv("CORE_HOME", home) // No agent-api.key file s := &PrepSubsystem{ @@ -252,7 +253,7 @@ func TestIngest_CreateIssueViaAPI_Bad_NoAPIKey(t *testing.T) { } // Should return early — no API key file - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.createIssueViaAPI("Title", "Body", "task", "normal") }) } @@ -265,8 +266,9 @@ func TestIngest_CreateIssueViaAPI_Bad_ServerError(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) - require.True(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK) - require.True(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK) + t.Setenv("CORE_HOME", home) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(home, ".claude")).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(home, ".claude", "agent-api.key"), "test-key").OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -277,7 +279,7 @@ func TestIngest_CreateIssueViaAPI_Bad_ServerError(t *testing.T) { } // Should not panic even on server error - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.createIssueViaAPI("Title", "Body", "task", "normal") }) } @@ -288,7 +290,7 @@ func TestIngest_CountFileRefs_Good_SecurityFindings(t *testing.T) { body := "Security scan found:\n" + "- `pkg/auth/token.go:55` hardcoded secret\n" + "- `pkg/auth/middleware.go:12` missing auth check\n" - assert.Equal(t, 2, countFileRefs(body)) + core.AssertEqual(t, 2, countFileRefs(body)) } // --- IngestFindings Ugly --- @@ -296,7 +298,7 @@ func TestIngest_CountFileRefs_Good_SecurityFindings(t *testing.T) { func TestIngest_IngestFindings_Ugly(t *testing.T) { // Workspace with no findings file (completed but empty meta dir) wsDir := t.TempDir() - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Agent: "codex", @@ -310,7 +312,7 @@ func TestIngest_IngestFindings_Ugly(t *testing.T) { } // Should return early without panic — no log files - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { s.ingestFindings(wsDir) }) } @@ -325,16 +327,17 @@ func TestIngest_CreateIssueViaAPI_Ugly(t *testing.T) { var body map[string]string core.JSONUnmarshalString(core.ReadAll(r.Body).Value.(string), &body) // Verify the body preserved HTML chars - assert.Contains(t, body["description"], "bold&", "bug", "high") - assert.True(t, called) + core.AssertTrue(t, called) } func TestIngest_CountFileRefs_Good_PHPSecurityFindings(t *testing.T) { @@ -353,5 +356,5 @@ func TestIngest_CountFileRefs_Good_PHPSecurityFindings(t *testing.T) { "- `src/Controller/Api.php:42` SQL injection risk\n" + "- `src/Service/Auth.php:100` session fixation\n" + "- `src/Config/routes.php:5` open redirect\n" - assert.Equal(t, 3, countFileRefs(body)) + core.AssertEqual(t, 3, countFileRefs(body)) } diff --git a/pkg/agentic/issue.go b/pkg/agentic/issue.go index c1a9916a..3b68a811 100644 --- a/pkg/agentic/issue.go +++ b/pkg/agentic/issue.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/issue_test.go b/pkg/agentic/issue_test.go index 2e153343..3a3db8e6 100644 --- a/pkg/agentic/issue_test.go +++ b/pkg/agentic/issue_test.go @@ -8,25 +8,23 @@ import ( "net/http/httptest" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestIssue_HandleIssueRecordCreate_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/issues", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "Fix auth", payload["title"]) - require.Equal(t, "bug", payload["type"]) - require.Equal(t, "codex", payload["assignee"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "Fix auth", payload["title"]) + core.AssertEqual(t, "bug", payload["type"]) + core.AssertEqual(t, "codex", payload["assignee"]) _, _ = w.Write([]byte(`{"data":{"slug":"fix-auth","title":"Fix auth","type":"bug","status":"open","priority":"high","assignee":"codex","labels":["auth"]}}`)) })) @@ -40,28 +38,28 @@ func TestIssue_HandleIssueRecordCreate_Good(t *testing.T) { core.Option{Key: "assignee", Value: "codex"}, core.Option{Key: "labels", Value: "auth"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(IssueOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "fix-auth", output.Issue.Slug) - assert.Equal(t, "open", output.Issue.Status) - assert.Equal(t, "codex", output.Issue.Assignee) - assert.Equal(t, []string{"auth"}, output.Issue.Labels) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "fix-auth", output.Issue.Slug) + core.AssertEqual(t, "open", output.Issue.Status) + core.AssertEqual(t, "codex", output.Issue.Assignee) + core.AssertEqual(t, []string{"auth"}, output.Issue.Labels) } func TestIssue_HandleIssueRecordGet_Bad(t *testing.T) { subsystem := testPrepWithPlatformServer(t, nil, "secret-token") result := subsystem.handleIssueRecordGet(context.Background(), core.NewOptions()) - assert.False(t, result.OK) - assert.EqualError(t, result.Value.(error), "issueGet: id or slug is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error), "issueGet: id or slug is required") } func TestIssue_HandleIssueRecordGet_Good_IDAlias(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues/42", r.URL.Path) + core.AssertEqual(t, "/v1/issues/42", r.URL.Path) _, _ = w.Write([]byte(`{"data":{"id":42,"slug":"fix-auth","title":"Fix auth","status":"open"}}`)) })) defer server.Close() @@ -70,22 +68,22 @@ func TestIssue_HandleIssueRecordGet_Good_IDAlias(t *testing.T) { result := subsystem.handleIssueRecordGet(context.Background(), core.NewOptions( core.Option{Key: "id", Value: "42"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(IssueOutput) - require.True(t, ok) - assert.Equal(t, 42, output.Issue.ID) - assert.Equal(t, "fix-auth", output.Issue.Slug) + core.RequireTrue(t, ok) + core.AssertEqual(t, 42, output.Issue.ID) + core.AssertEqual(t, "fix-auth", output.Issue.Slug) } func TestIssue_HandleIssueRecordList_Good_Filters(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues", r.URL.Path) - require.Equal(t, "open", r.URL.Query().Get("status")) - require.Equal(t, "bug", r.URL.Query().Get("type")) - require.Equal(t, "high", r.URL.Query().Get("priority")) - require.Equal(t, "codex", r.URL.Query().Get("assignee")) - require.Equal(t, []string{"auth", "backend"}, r.URL.Query()["labels"]) + core.AssertEqual(t, "/v1/issues", r.URL.Path) + core.AssertEqual(t, "open", r.URL.Query().Get("status")) + core.AssertEqual(t, "bug", r.URL.Query().Get("type")) + core.AssertEqual(t, "high", r.URL.Query().Get("priority")) + core.AssertEqual(t, "codex", r.URL.Query().Get("assignee")) + core.AssertEqual(t, []string{"auth", "backend"}, r.URL.Query()["labels"]) _, _ = w.Write([]byte(`{"data":{"issues":[{"id":7,"workspace_id":3,"sprint_id":5,"slug":"fix-auth","title":"Fix auth","labels":["auth","backend"]}],"total":1}}`)) })) defer server.Close() @@ -98,18 +96,18 @@ func TestIssue_HandleIssueRecordList_Good_Filters(t *testing.T) { core.Option{Key: "assignee", Value: "codex"}, core.Option{Key: "labels", Value: []string{"auth", "backend"}}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(IssueListOutput) - require.True(t, ok) - assert.Len(t, output.Issues, 1) - assert.Equal(t, 1, output.Count) - assert.Equal(t, []string{"auth", "backend"}, output.Issues[0].Labels) + core.RequireTrue(t, ok) + core.AssertLen(t, output.Issues, 1) + core.AssertEqual(t, 1, output.Count) + core.AssertEqual(t, []string{"auth", "backend"}, output.Issues[0].Labels) } func TestIssue_HandleIssueRecordList_Bad_ServerError(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues", r.URL.Path) + core.AssertEqual(t, "/v1/issues", r.URL.Path) w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"error":"backend offline"}`)) })) @@ -119,23 +117,23 @@ func TestIssue_HandleIssueRecordList_Bad_ServerError(t *testing.T) { result := subsystem.handleIssueRecordList(context.Background(), core.NewOptions( core.Option{Key: "status", Value: "open"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "issue.list") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "issue.list") } func TestIssue_HandleIssueRecordAssign_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues/fix-auth", r.URL.Path) - require.Equal(t, http.MethodPatch, r.Method) + core.AssertEqual(t, "/v1/issues/fix-auth", r.URL.Path) + core.AssertEqual(t, http.MethodPatch, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - require.Equal(t, "codex", payload["assignee"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "codex", payload["assignee"]) _, _ = w.Write([]byte(`{"data":{"issue":{"slug":"fix-auth","title":"Fix auth","status":"assigned","assignee":"codex"}}}`)) })) @@ -146,13 +144,13 @@ func TestIssue_HandleIssueRecordAssign_Good(t *testing.T) { core.Option{Key: "slug", Value: "fix-auth"}, core.Option{Key: "assignee", Value: "codex"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(IssueOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "codex", output.Issue.Assignee) - assert.Equal(t, "assigned", output.Issue.Status) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "codex", output.Issue.Assignee) + core.AssertEqual(t, "assigned", output.Issue.Status) } func TestIssue_HandleIssueRecordAssign_Bad_MissingAssignee(t *testing.T) { @@ -161,8 +159,8 @@ func TestIssue_HandleIssueRecordAssign_Bad_MissingAssignee(t *testing.T) { result := subsystem.handleIssueRecordAssign(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: "fix-auth"}, )) - assert.False(t, result.OK) - assert.EqualError(t, result.Value.(error), "issueAssign: assignee is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error), "issueAssign: assignee is required") } func TestIssue_HandleIssueRecordAssign_Ugly_MissingIdentifier(t *testing.T) { @@ -171,22 +169,22 @@ func TestIssue_HandleIssueRecordAssign_Ugly_MissingIdentifier(t *testing.T) { result := subsystem.handleIssueRecordAssign(context.Background(), core.NewOptions( core.Option{Key: "assignee", Value: "codex"}, )) - assert.False(t, result.OK) - assert.EqualError(t, result.Value.(error), "issueAssign: id or slug is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error), "issueAssign: id or slug is required") } func TestIssue_HandleIssueRecordReport_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues/fix-auth/comments", r.URL.Path) - require.Equal(t, http.MethodPost, r.Method) + core.AssertEqual(t, "/v1/issues/fix-auth/comments", r.URL.Path) + core.AssertEqual(t, http.MethodPost, r.Method) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload) - require.True(t, parseResult.OK) - assert.Equal(t, "QA failed: build output changed", payload["body"]) - assert.Equal(t, "codex", payload["author"]) + core.RequireTrue(t, parseResult.OK) + core.AssertEqual(t, "QA failed: build output changed", payload["body"]) + core.AssertEqual(t, "codex", payload["author"]) _, _ = w.Write([]byte(`{"data":{"comment":{"id":88,"issue_id":42,"author":"codex","body":"QA failed: build output changed","metadata":{"source":"qa"}}}}`)) })) @@ -199,15 +197,15 @@ func TestIssue_HandleIssueRecordReport_Good(t *testing.T) { core.Option{Key: "author", Value: "codex"}, core.Option{Key: "metadata", Value: map[string]any{"source": "qa"}}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(IssueReportOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, 88, output.Comment.ID) - assert.Equal(t, "QA failed: build output changed", output.Comment.Body) - assert.Equal(t, "codex", output.Comment.Author) - assert.Equal(t, map[string]any{"source": "qa"}, output.Comment.Metadata) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, 88, output.Comment.ID) + core.AssertEqual(t, "QA failed: build output changed", output.Comment.Body) + core.AssertEqual(t, "codex", output.Comment.Author) + core.AssertEqual(t, map[string]any{"source": "qa"}, output.Comment.Metadata) } func TestIssue_HandleIssueRecordReport_Bad_MissingReport(t *testing.T) { @@ -216,8 +214,8 @@ func TestIssue_HandleIssueRecordReport_Bad_MissingReport(t *testing.T) { result := subsystem.handleIssueRecordReport(context.Background(), core.NewOptions( core.Option{Key: "slug", Value: "fix-auth"}, )) - assert.False(t, result.OK) - assert.EqualError(t, result.Value.(error), "issueReport: report is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error), "issueReport: report is required") } func TestIssue_HandleIssueRecordReport_Ugly_MissingIdentifier(t *testing.T) { @@ -226,14 +224,14 @@ func TestIssue_HandleIssueRecordReport_Ugly_MissingIdentifier(t *testing.T) { result := subsystem.handleIssueRecordReport(context.Background(), core.NewOptions( core.Option{Key: "report", Value: "QA failed: build output changed"}, )) - assert.False(t, result.OK) - assert.EqualError(t, result.Value.(error), "issueReport: issue_id, id, or slug is required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error), "issueReport: issue_id, id, or slug is required") } func TestIssue_HandleIssueRecordList_Ugly_NestedEnvelope(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/v1/issues", r.URL.Path) - require.Equal(t, "open", r.URL.Query().Get("status")) + core.AssertEqual(t, "/v1/issues", r.URL.Path) + core.AssertEqual(t, "open", r.URL.Query().Get("status")) _, _ = w.Write([]byte(`{"data":{"issues":[{"id":7,"workspace_id":3,"sprint_id":5,"slug":"fix-auth","title":"Fix auth","labels":["auth","backend"]}],"total":1}}`)) })) defer server.Close() @@ -242,13 +240,13 @@ func TestIssue_HandleIssueRecordList_Ugly_NestedEnvelope(t *testing.T) { result := subsystem.handleIssueRecordList(context.Background(), core.NewOptions( core.Option{Key: "status", Value: "open"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(IssueListOutput) - require.True(t, ok) - require.Len(t, output.Issues, 1) - assert.Equal(t, 1, output.Count) - assert.Equal(t, 3, output.Issues[0].WorkspaceID) - assert.Equal(t, 5, output.Issues[0].SprintID) - assert.Equal(t, []string{"auth", "backend"}, output.Issues[0].Labels) + core.RequireTrue(t, ok) + core.AssertLen(t, output.Issues, 1) + core.AssertEqual(t, 1, output.Count) + core.AssertEqual(t, 3, output.Issues[0].WorkspaceID) + core.AssertEqual(t, 5, output.Issues[0].SprintID) + core.AssertEqual(t, []string{"auth", "backend"}, output.Issues[0].Labels) } diff --git a/pkg/agentic/lang.go b/pkg/agentic/lang.go index 334459f5..18af03a2 100644 --- a/pkg/agentic/lang.go +++ b/pkg/agentic/lang.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/lang_test.go b/pkg/agentic/lang_test.go index 8625e73b..a8933809 100644 --- a/pkg/agentic/lang_test.go +++ b/pkg/agentic/lang_test.go @@ -6,76 +6,74 @@ import ( "context" "testing" - core "dappco.re/go/core" + core "dappco.re/go" "github.com/modelcontextprotocol/go-sdk/mcp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestLang_CmdLangDetect_Good_Go(t *testing.T) { s, _ := testPrepWithCore(t, nil) dir := t.TempDir() - require.True(t, fs.Write(core.JoinPath(dir, "go.mod"), "module example").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "go.mod"), "module example").OK) r := s.cmdLangDetect(core.NewOptions(core.Option{Key: "_arg", Value: dir})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(LanguageDetectOutput) - require.True(t, ok) - assert.Equal(t, dir, output.Path) - assert.Equal(t, "go", output.Language) + core.RequireTrue(t, ok) + core.AssertEqual(t, dir, output.Path) + core.AssertEqual(t, "go", output.Language) } func TestLang_CmdLangDetect_Bad_MissingPath(t *testing.T) { s, _ := testPrepWithCore(t, nil) r := s.cmdLangDetect(core.NewOptions()) - assert.False(t, r.OK) + core.AssertFalse(t, r.OK) } func TestLang_CmdLangDetect_Ugly_PreferenceOrder(t *testing.T) { s, _ := testPrepWithCore(t, nil) dir := t.TempDir() - require.True(t, fs.Write(core.JoinPath(dir, "go.mod"), "module example").OK) - require.True(t, fs.Write(core.JoinPath(dir, "package.json"), "{}").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "go.mod"), "module example").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "package.json"), "{}").OK) r := s.cmdLangDetect(core.NewOptions(core.Option{Key: "_arg", Value: dir})) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) output, ok := r.Value.(LanguageDetectOutput) - require.True(t, ok) - assert.Equal(t, "go", output.Language) + core.RequireTrue(t, ok) + core.AssertEqual(t, "go", output.Language) } func TestLang_LangDetect_Good_PHP(t *testing.T) { s, _ := testPrepWithCore(t, nil) dir := t.TempDir() - require.True(t, fs.Write(core.JoinPath(dir, "composer.json"), "{}").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "composer.json"), "{}").OK) _, output, err := s.langDetect(context.Background(), (*mcp.CallToolRequest)(nil), LanguageDetectInput{Path: dir}) - require.NoError(t, err) - assert.Equal(t, dir, output.Path) - assert.Equal(t, "php", output.Language) + core.RequireNoError(t, err) + core.AssertEqual(t, dir, output.Path) + core.AssertEqual(t, "php", output.Language) } func TestLang_LangList_Good(t *testing.T) { s, _ := testPrepWithCore(t, nil) _, output, err := s.langList(context.Background(), (*mcp.CallToolRequest)(nil), LanguageListInput{}) - require.NoError(t, err) - require.True(t, output.Success) - assert.Equal(t, len(supportedLanguages), output.Count) - assert.Equal(t, supportedLanguages, output.Languages) + core.RequireNoError(t, err) + core.RequireTrue(t, output.Success) + core.AssertEqual(t, len(supportedLanguages), output.Count) + core.AssertEqual(t, supportedLanguages, output.Languages) } func TestLang_LangList_Ugly_CopyIsolation(t *testing.T) { s, _ := testPrepWithCore(t, nil) _, first, err := s.langList(context.Background(), (*mcp.CallToolRequest)(nil), LanguageListInput{}) - require.NoError(t, err) - require.NotEmpty(t, first.Languages) + core.RequireNoError(t, err) + core.RequireNotEmpty(t, first.Languages) first.Languages[0] = "mutated" _, second, err := s.langList(context.Background(), (*mcp.CallToolRequest)(nil), LanguageListInput{}) - require.NoError(t, err) - assert.Equal(t, supportedLanguages[0], second.Languages[0]) + core.RequireNoError(t, err) + core.AssertEqual(t, supportedLanguages[0], second.Languages[0]) } diff --git a/pkg/agentic/logic_test.go b/pkg/agentic/logic_test.go index 90377b31..1e699025 100644 --- a/pkg/agentic/logic_test.go +++ b/pkg/agentic/logic_test.go @@ -6,180 +6,182 @@ import ( "strings" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) // --- agentCommand --- func TestDispatch_AgentCommand_Good_Gemini(t *testing.T) { cmd, args, err := agentCommand("gemini", "do the thing") - require.NoError(t, err) - assert.Equal(t, "gemini", cmd) - assert.Contains(t, args, "-p") - assert.Contains(t, args, "do the thing") - assert.Contains(t, args, "--yolo") - assert.Contains(t, args, "--sandbox") + core.RequireNoError(t, err) + core.AssertEqual(t, "gemini", cmd) + core.AssertContains(t, args, "-p") + core.AssertContains(t, args, "do the thing") + core.AssertContains(t, args, "--yolo") + core.AssertContains(t, args, "--sandbox") } func TestDispatch_AgentCommand_Good_GeminiWithModel(t *testing.T) { cmd, args, err := agentCommand("gemini:flash", "my prompt") - require.NoError(t, err) - assert.Equal(t, "gemini", cmd) - assert.Contains(t, args, "-m") - assert.Contains(t, args, "gemini-2.5-flash") + core.RequireNoError(t, err) + core.AssertEqual(t, "gemini", cmd) + core.AssertContains(t, args, "-m") + core.AssertContains(t, args, "gemini-2.5-flash") } func TestDispatch_AgentCommand_Good_Codex(t *testing.T) { cmd, args, err := agentCommand("codex", "fix the tests") - require.NoError(t, err) - assert.Equal(t, "codex", cmd) - assert.Contains(t, args, "exec") - assert.Contains(t, args, "--dangerously-bypass-approvals-and-sandbox") - assert.Contains(t, args, "fix the tests") + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", cmd) + core.AssertContains(t, args, "exec") + core.AssertContains(t, args, "--dangerously-bypass-approvals-and-sandbox") + core.AssertContains(t, args, "fix the tests") } func TestDispatch_AgentCommand_Good_CodexReview(t *testing.T) { cmd, args, err := agentCommand("codex:review", "") - require.NoError(t, err) - assert.Equal(t, "codex", cmd) - assert.Contains(t, args, "exec") + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", cmd) + core.AssertContains(t, args, "exec") // Review mode should NOT include -o flag for _, a := range args { - assert.NotEqual(t, "-o", a) + core.AssertNotEqual(t, "-o", a) } } func TestDispatch_AgentCommand_Good_CodexWithModel(t *testing.T) { cmd, args, err := agentCommand("codex:gpt-5.4", "refactor this") - require.NoError(t, err) - assert.Equal(t, "codex", cmd) - assert.Contains(t, args, "--model") - assert.Contains(t, args, "gpt-5.4") + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", cmd) + core.AssertContains(t, args, "--model") + core.AssertContains(t, args, "gpt-5.4") } func TestDispatch_AgentCommand_Good_Claude(t *testing.T) { cmd, args, err := agentCommand("claude", "add tests") - require.NoError(t, err) - assert.Equal(t, "claude", cmd) - assert.Contains(t, args, "-p") - assert.Contains(t, args, "add tests") - assert.Contains(t, args, "--dangerously-skip-permissions") + core.RequireNoError(t, err) + core.AssertEqual(t, "claude", cmd) + core.AssertContains(t, args, "-p") + core.AssertContains(t, args, "add tests") + core.AssertContains(t, args, "--dangerously-skip-permissions") } func TestDispatch_AgentCommand_Good_ClaudeWithModel(t *testing.T) { cmd, args, err := agentCommand("claude:haiku", "write docs") - require.NoError(t, err) - assert.Equal(t, "claude", cmd) - assert.Contains(t, args, "--model") - assert.Contains(t, args, "haiku") + core.RequireNoError(t, err) + core.AssertEqual(t, "claude", cmd) + core.AssertContains(t, args, "--model") + core.AssertContains(t, args, "haiku") } func TestDispatch_AgentCommand_Good_CodeRabbit(t *testing.T) { cmd, args, err := agentCommand("coderabbit", "") - require.NoError(t, err) - assert.Equal(t, "coderabbit", cmd) - assert.Contains(t, args, "review") - assert.Contains(t, args, "--plain") + core.RequireNoError(t, err) + core.AssertEqual(t, "coderabbit", cmd) + core.AssertContains(t, args, "review") + core.AssertContains(t, args, "--plain") } func TestDispatch_AgentCommand_Good_Local(t *testing.T) { cmd, args, err := agentCommand("local", "do stuff") - require.NoError(t, err) - assert.Equal(t, "sh", cmd) - assert.Equal(t, "-c", args[0]) + core.RequireNoError(t, err) + core.AssertEqual(t, "sh", cmd) + core.AssertEqual(t, "-c", args[0]) // Script should contain socat proxy setup - assert.Contains(t, args[1], "socat") - assert.Contains(t, args[1], "devstral-24b") + core.AssertContains(t, args[1], "socat") + core.AssertContains(t, args[1], "devstral-24b") } func TestDispatch_AgentCommand_Good_LocalWithModel(t *testing.T) { cmd, args, err := agentCommand("local:mistral-nemo", "do stuff") - require.NoError(t, err) - assert.Equal(t, "sh", cmd) - assert.Contains(t, args[1], "mistral-nemo") + core.RequireNoError(t, err) + core.AssertEqual(t, "sh", cmd) + core.AssertContains(t, args[1], "mistral-nemo") } func TestDispatch_LocalAgentCommandScript_Good_ShellQuoting(t *testing.T) { script := localAgentCommandScript("devstral-24b", "can't break quoting") - assert.Contains(t, script, "'can'\\''t break quoting'") + core.AssertContains( + t, + script, + "'can'\\''t break quoting'", + ) } func TestDispatch_AgentCommand_Good_CodexLEMProfile(t *testing.T) { cmd, args, err := agentCommand("codex:lemmy", "implement the scorer") - require.NoError(t, err) - assert.Equal(t, "codex", cmd) - assert.Contains(t, args, "--profile") - assert.Contains(t, args, "lemmy") - assert.NotContains(t, args, "--model") + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", cmd) + core.AssertContains(t, args, "--profile") + core.AssertContains(t, args, "lemmy") + core.AssertNotContains(t, args, "--model") } func TestDispatch_AgentCommand_Good_CodexLemer(t *testing.T) { cmd, args, err := agentCommand("codex:lemer", "add docs") - require.NoError(t, err) - assert.Equal(t, "codex", cmd) - assert.Contains(t, args, "--profile") - assert.Contains(t, args, "lemer") + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", cmd) + core.AssertContains(t, args, "--profile") + core.AssertContains(t, args, "lemer") } func TestDispatch_AgentCommand_Good_CodexLemrd(t *testing.T) { cmd, args, err := agentCommand("codex:lemrd", "review code") - require.NoError(t, err) - assert.Equal(t, "codex", cmd) - assert.Contains(t, args, "--profile") - assert.Contains(t, args, "lemrd") + core.RequireNoError(t, err) + core.AssertEqual(t, "codex", cmd) + core.AssertContains(t, args, "--profile") + core.AssertContains(t, args, "lemrd") } func TestDispatch_IsLEMProfile_Good(t *testing.T) { - assert.True(t, isLEMProfile("lemer")) - assert.True(t, isLEMProfile("lemma")) - assert.True(t, isLEMProfile("lemmy")) - assert.True(t, isLEMProfile("lemrd")) + core.AssertTrue(t, isLEMProfile("lemer")) + core.AssertTrue(t, isLEMProfile("lemma")) + core.AssertTrue(t, isLEMProfile("lemmy")) + core.AssertTrue(t, isLEMProfile("lemrd")) } func TestDispatch_IsLEMProfile_Bad(t *testing.T) { - assert.False(t, isLEMProfile("gpt-5.4")) - assert.False(t, isLEMProfile("gemini-2.5-flash")) - assert.False(t, isLEMProfile("")) + core.AssertFalse(t, isLEMProfile("gpt-5.4")) + core.AssertFalse(t, isLEMProfile("gemini-2.5-flash")) + core.AssertFalse(t, isLEMProfile("")) } func TestDispatch_IsLEMProfile_Ugly(t *testing.T) { - assert.False(t, isLEMProfile("Lemmy")) - assert.False(t, isLEMProfile("LEMRD")) - assert.False(t, isLEMProfile("lem")) + core.AssertFalse(t, isLEMProfile("Lemmy")) + core.AssertFalse(t, isLEMProfile("LEMRD")) + core.AssertFalse(t, isLEMProfile("lem")) } func TestDispatch_IsNativeAgent_Good(t *testing.T) { - assert.True(t, isNativeAgent("claude")) - assert.True(t, isNativeAgent("claude:opus")) - assert.True(t, isNativeAgent("claude:haiku")) + core.AssertTrue(t, isNativeAgent("claude")) + core.AssertTrue(t, isNativeAgent("claude:opus")) + core.AssertTrue(t, isNativeAgent("claude:haiku")) } func TestDispatch_IsNativeAgent_Bad(t *testing.T) { - assert.False(t, isNativeAgent("codex")) - assert.False(t, isNativeAgent("codex:gpt-5.4")) - assert.False(t, isNativeAgent("gemini")) + core.AssertFalse(t, isNativeAgent("codex")) + core.AssertFalse(t, isNativeAgent("codex:gpt-5.4")) + core.AssertFalse(t, isNativeAgent("gemini")) } func TestDispatch_IsNativeAgent_Ugly(t *testing.T) { - assert.False(t, isNativeAgent("")) - assert.False(t, isNativeAgent("codex:lemmy")) - assert.False(t, isNativeAgent("local:mistral")) + core.AssertFalse(t, isNativeAgent("")) + core.AssertFalse(t, isNativeAgent("codex:lemmy")) + core.AssertFalse(t, isNativeAgent("local:mistral")) } func TestDispatch_AgentCommand_Bad_Unknown(t *testing.T) { cmd, args, err := agentCommand("robot-from-the-future", "take over") - assert.Error(t, err) - assert.Empty(t, cmd) - assert.Nil(t, args) + core.AssertError(t, err) + core.AssertEmpty(t, cmd) + core.AssertNil(t, args) } func TestDispatch_AgentCommand_Ugly_EmptyAgent(t *testing.T) { cmd, args, err := agentCommand("", "prompt") - assert.Error(t, err) - assert.Empty(t, cmd) - assert.Nil(t, args) + core.AssertError(t, err) + core.AssertEmpty(t, cmd) + core.AssertNil(t, args) } // --- containerCommand --- @@ -189,18 +191,18 @@ func TestDispatch_ContainerCommand_Good_Codex(t *testing.T) { t.Setenv("DIR_HOME", "/home/dev") cmd, args := containerCommand("codex", []string{"exec", "--dangerously-bypass-approvals-and-sandbox", "do it"}, "/ws", "/ws/.meta") - assert.Equal(t, "docker", cmd) - assert.Contains(t, args, "run") - assert.Contains(t, args, "--rm") - assert.Contains(t, args, "/ws:/workspace") - assert.Contains(t, args, "/ws/.meta:/workspace/.meta") - assert.Contains(t, args, "/workspace/repo") + core.AssertEqual(t, "docker", cmd) + core.AssertContains(t, args, "run") + core.AssertContains(t, args, "--rm") + core.AssertContains(t, args, "/ws:/workspace") + core.AssertContains(t, args, "/ws/.meta:/workspace/.meta") + core.AssertContains(t, args, "/workspace/repo") // Command is wrapped in sh -c for chmod cleanup shCmd := args[len(args)-1] - assert.Contains(t, shCmd, "missing /workspace/repo") - assert.Contains(t, shCmd, "codex") + core.AssertContains(t, shCmd, "missing /workspace/repo") + core.AssertContains(t, shCmd, "codex") // Should use default image - assert.Contains(t, args, defaultDockerImage) + core.AssertContains(t, args, defaultDockerImage) } func TestDispatch_ContainerCommand_Good_CustomImage(t *testing.T) { @@ -208,8 +210,8 @@ func TestDispatch_ContainerCommand_Good_CustomImage(t *testing.T) { t.Setenv("DIR_HOME", "/home/dev") cmd, args := containerCommand("codex", []string{"exec"}, "/ws", "/ws/.meta") - assert.Equal(t, "docker", cmd) - assert.Contains(t, args, "my-custom-image:latest") + core.AssertEqual(t, "docker", cmd) + core.AssertContains(t, args, "my-custom-image:latest") } func TestDispatch_ContainerCommand_Good_ClaudeMountsConfig(t *testing.T) { @@ -218,7 +220,7 @@ func TestDispatch_ContainerCommand_Good_ClaudeMountsConfig(t *testing.T) { _, args := containerCommand("claude", []string{"-p", "do it"}, "/ws", "/ws/.meta") joined := strings.Join(args, " ") - assert.Contains(t, joined, ".claude:/home/agent/.claude:ro") + core.AssertContains(t, joined, ".claude:/home/agent/.claude:ro") } func TestDispatch_ContainerCommand_Good_GeminiMountsConfig(t *testing.T) { @@ -227,7 +229,7 @@ func TestDispatch_ContainerCommand_Good_GeminiMountsConfig(t *testing.T) { _, args := containerCommand("gemini", []string{"-p", "do it"}, "/ws", "/ws/.meta") joined := strings.Join(args, " ") - assert.Contains(t, joined, ".gemini:/home/agent/.gemini:ro") + core.AssertContains(t, joined, ".gemini:/home/agent/.gemini:ro") } func TestDispatch_ContainerCommand_Good_CodexNoClaudeMount(t *testing.T) { @@ -237,7 +239,7 @@ func TestDispatch_ContainerCommand_Good_CodexNoClaudeMount(t *testing.T) { _, args := containerCommand("codex", []string{"exec"}, "/ws", "/ws/.meta") joined := strings.Join(args, " ") // codex agent must NOT mount .claude config - assert.NotContains(t, joined, ".claude:/home/agent/.claude:ro") + core.AssertNotContains(t, joined, ".claude:/home/agent/.claude:ro") } func TestDispatch_ContainerCommand_Good_APIKeysPassedByRef(t *testing.T) { @@ -246,9 +248,9 @@ func TestDispatch_ContainerCommand_Good_APIKeysPassedByRef(t *testing.T) { _, args := containerCommand("codex", []string{"exec"}, "/ws", "/ws/.meta") joined := strings.Join(args, " ") - assert.Contains(t, joined, "OPENAI_API_KEY") - assert.Contains(t, joined, "ANTHROPIC_API_KEY") - assert.Contains(t, joined, "GEMINI_API_KEY") + core.AssertContains(t, joined, "OPENAI_API_KEY") + core.AssertContains(t, joined, "ANTHROPIC_API_KEY") + core.AssertContains(t, joined, "GEMINI_API_KEY") } func TestDispatch_ContainerCommand_Ugly_EmptyDirs(t *testing.T) { @@ -257,8 +259,8 @@ func TestDispatch_ContainerCommand_Ugly_EmptyDirs(t *testing.T) { // Should not panic with empty paths cmd, args := containerCommand("codex", []string{"exec"}, "", "") - assert.Equal(t, "docker", cmd) - assert.NotEmpty(t, args) + core.AssertEqual(t, "docker", cmd) + core.AssertNotEmpty(t, args) } // --- buildAutoPRBody --- @@ -271,11 +273,11 @@ func TestAutopr_BuildAutoPRBody_Good_Basic(t *testing.T) { Branch: "agent/fix-login-bug", } body := s.buildAutoPRBody(st, 3) - assert.Contains(t, body, "Fix the login bug") - assert.Contains(t, body, "codex") - assert.Contains(t, body, "3") - assert.Contains(t, body, "agent/fix-login-bug") - assert.Contains(t, body, "Co-Authored-By: Virgil ") + core.AssertContains(t, body, "Fix the login bug") + core.AssertContains(t, body, "codex") + core.AssertContains(t, body, "3") + core.AssertContains(t, body, "agent/fix-login-bug") + core.AssertContains(t, body, "Co-Authored-By: Virgil ") } func TestAutopr_BuildAutoPRBody_Good_WithIssue(t *testing.T) { @@ -287,7 +289,7 @@ func TestAutopr_BuildAutoPRBody_Good_WithIssue(t *testing.T) { Issue: 42, } body := s.buildAutoPRBody(st, 1) - assert.Contains(t, body, "Closes #42") + core.AssertContains(t, body, "Closes #42") } func TestAutopr_BuildAutoPRBody_Good_NoIssue(t *testing.T) { @@ -298,7 +300,7 @@ func TestAutopr_BuildAutoPRBody_Good_NoIssue(t *testing.T) { Branch: "agent/refactor-internals", } body := s.buildAutoPRBody(st, 5) - assert.NotContains(t, body, "Closes #") + core.AssertNotContains(t, body, "Closes #") } func TestAutopr_BuildAutoPRBody_Good_CommitCount(t *testing.T) { @@ -306,8 +308,8 @@ func TestAutopr_BuildAutoPRBody_Good_CommitCount(t *testing.T) { st := &WorkspaceStatus{Agent: "codex", Branch: "agent/foo"} body1 := s.buildAutoPRBody(st, 1) body5 := s.buildAutoPRBody(st, 5) - assert.Contains(t, body1, "**Commits:** 1") - assert.Contains(t, body5, "**Commits:** 5") + core.AssertContains(t, body1, "**Commits:** 1") + core.AssertContains(t, body5, "**Commits:** 5") } func TestAutopr_BuildAutoPRBody_Bad_EmptyTask(t *testing.T) { @@ -319,15 +321,15 @@ func TestAutopr_BuildAutoPRBody_Bad_EmptyTask(t *testing.T) { } // Should not panic; body should still have the structure body := s.buildAutoPRBody(st, 0) - assert.Contains(t, body, "## Task") - assert.Contains(t, body, "**Agent:** codex") + core.AssertContains(t, body, "## Task") + core.AssertContains(t, body, "**Agent:** codex") } func TestAutopr_BuildAutoPRBody_Ugly_ZeroCommits(t *testing.T) { s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{})} st := &WorkspaceStatus{Agent: "codex", Branch: "agent/test"} body := s.buildAutoPRBody(st, 0) - assert.Contains(t, body, "**Commits:** 0") + core.AssertContains(t, body, "**Commits:** 0") } // --- emitEvent --- @@ -335,53 +337,53 @@ func TestAutopr_BuildAutoPRBody_Ugly_ZeroCommits(t *testing.T) { func TestEvents_EmitEvent_Good_WritesJSONL(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitEvent("agent_completed", "codex", "core/go-io/task-5", "completed") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK, "events.jsonl should exist after emitEvent") + core.RequireTrue(t, r.OK, "events.jsonl should exist after emitEvent") content := r.Value.(string) - assert.Contains(t, content, "agent_completed") - assert.Contains(t, content, "codex") - assert.Contains(t, content, "core/go-io/task-5") - assert.Contains(t, content, "completed") + core.AssertContains(t, content, "agent_completed") + core.AssertContains(t, content, "codex") + core.AssertContains(t, content, "core/go-io/task-5") + core.AssertContains(t, content, "completed") } func TestEvents_EmitEvent_Good_ValidJSON(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitEvent("agent_started", "claude", "core/agent/task-1", "running") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") content := fs.Read(eventsFile) - require.True(t, content.OK) + core.RequireTrue(t, content.OK) for _, line := range core.Split(content.Value.(string), "\n") { if line == "" { continue } var ev CompletionEvent - require.True(t, core.JSONUnmarshalString(line, &ev).OK, "each line must be valid JSON") - assert.Equal(t, "agent_started", ev.Type) + core.RequireTrue(t, core.JSONUnmarshalString(line, &ev).OK, "each line must be valid JSON") + core.AssertEqual(t, "agent_started", ev.Type) } } func TestEvents_EmitEvent_Good_Appends(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitEvent("agent_started", "codex", "core/go-io/task-1", "running") emitEvent("agent_completed", "codex", "core/go-io/task-1", "completed") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) lines := 0 for _, line := range strings.Split(strings.TrimSpace(r.Value.(string)), "\n") { @@ -389,35 +391,35 @@ func TestEvents_EmitEvent_Good_Appends(t *testing.T) { lines++ } } - assert.Equal(t, 2, lines, "both events should be in the log") + core.AssertEqual(t, 2, lines, "both events should be in the log") } func TestEvents_EmitEvent_Good_StartHelper(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitStartEvent("gemini", "core/go-log/task-3") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) - assert.Contains(t, r.Value.(string), "agent_started") - assert.Contains(t, r.Value.(string), "running") + core.RequireTrue(t, r.OK) + core.AssertContains(t, r.Value.(string), "agent_started") + core.AssertContains(t, r.Value.(string), "running") } func TestEvents_EmitEvent_Good_CompletionHelper(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitCompletionEvent("claude", "core/agent/task-7", "failed") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) - assert.Contains(t, r.Value.(string), "agent_completed") - assert.Contains(t, r.Value.(string), "failed") + core.RequireTrue(t, r.OK) + core.AssertContains(t, r.Value.(string), "agent_completed") + core.AssertContains(t, r.Value.(string), "failed") } func TestEvents_EmitEvent_Bad_NoWorkspaceDir(t *testing.T) { @@ -426,7 +428,7 @@ func TestEvents_EmitEvent_Bad_NoWorkspaceDir(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) // Do NOT create workspace/ subdir — emitEvent must handle this gracefully - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitEvent("agent_completed", "codex", "test", "completed") }) } @@ -434,10 +436,10 @@ func TestEvents_EmitEvent_Bad_NoWorkspaceDir(t *testing.T) { func TestEvents_EmitEvent_Ugly_EmptyFields(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) // Should not panic with all empty fields - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitEvent("", "", "", "") }) } @@ -447,142 +449,170 @@ func TestEvents_EmitEvent_Ugly_EmptyFields(t *testing.T) { func TestEvents_EmitStartEvent_Good(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitStartEvent("codex", "core/go-io/task-10") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) content := r.Value.(string) - assert.Contains(t, content, "agent_started") - assert.Contains(t, content, "codex") - assert.Contains(t, content, "core/go-io/task-10") + core.AssertContains(t, content, "agent_started") + core.AssertContains(t, content, "codex") + core.AssertContains(t, content, "core/go-io/task-10") } func TestEvents_EmitStartEvent_Bad(t *testing.T) { // Empty agent name root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitStartEvent("", "core/go-io/task-10") }) eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) content := r.Value.(string) - assert.Contains(t, content, "agent_started") + core.AssertContains(t, content, "agent_started") } func TestEvents_EmitStartEvent_Ugly(t *testing.T) { // Very long workspace name root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) longName := strings.Repeat("very-long-workspace-name-", 50) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitStartEvent("claude", longName) }) eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) - assert.Contains(t, r.Value.(string), "agent_started") + core.RequireTrue(t, r.OK) + core.AssertContains(t, r.Value.(string), "agent_started") } func TestEvents_EmitCompletionEvent_Good(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) emitCompletionEvent("gemini", "core/go-log/task-5", "completed") eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) + core.RequireTrue(t, r.OK) content := r.Value.(string) - assert.Contains(t, content, "agent_completed") - assert.Contains(t, content, "gemini") - assert.Contains(t, content, "completed") + core.AssertContains(t, content, "agent_completed") + core.AssertContains(t, content, "gemini") + core.AssertContains(t, content, "completed") } func TestEvents_EmitCompletionEvent_Bad(t *testing.T) { // Empty status root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitCompletionEvent("claude", "core/agent/task-1", "") }) eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) - assert.Contains(t, r.Value.(string), "agent_completed") + core.RequireTrue(t, r.OK) + core.AssertContains(t, r.Value.(string), "agent_completed") } func TestEvents_EmitCompletionEvent_Ugly(t *testing.T) { // Unicode in agent name root := t.TempDir() setTestWorkspace(t, root) - require.True(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(root, "workspace")).OK) - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { emitCompletionEvent("\u00e9nchantr\u00efx-\u2603", "core/agent/task-1", "completed") }) eventsFile := core.JoinPath(root, "workspace", "events.jsonl") r := fs.Read(eventsFile) - require.True(t, r.OK) - assert.Contains(t, r.Value.(string), "\u00e9nchantr\u00efx") + core.RequireTrue(t, r.OK) + core.AssertContains(t, r.Value.(string), "\u00e9nchantr\u00efx") } // --- countFileRefs --- func TestIngest_CountFileRefs_Good_GoRefs(t *testing.T) { body := "Found issue in `pkg/core/app.go:42` and `pkg/core/service.go:100`." - assert.Equal(t, 2, countFileRefs(body)) + core.AssertEqual( + t, + 2, + countFileRefs(body), + ) } func TestIngest_CountFileRefs_Good_PHPRefs(t *testing.T) { body := "See `src/Core/Boot.php:15` for details." - assert.Equal(t, 1, countFileRefs(body)) + core.AssertEqual( + t, + 1, + countFileRefs(body), + ) } func TestIngest_CountFileRefs_Good_Mixed(t *testing.T) { body := "Go file: `main.go:1`, PHP file: `index.php:99`, plain text ref." - assert.Equal(t, 2, countFileRefs(body)) + core.AssertEqual( + t, + 2, + countFileRefs(body), + ) } func TestIngest_CountFileRefs_Good_NoRefs(t *testing.T) { body := "This is just plain text with no file references." - assert.Equal(t, 0, countFileRefs(body)) + core.AssertEqual( + t, + 0, + countFileRefs(body), + ) } func TestIngest_CountFileRefs_Good_UnrelatedBacktick(t *testing.T) { // Backtick-quoted string that is not a file:line reference body := "Run `go test ./...` to execute tests." - assert.Equal(t, 0, countFileRefs(body)) + core.AssertEqual( + t, + 0, + countFileRefs(body), + ) } func TestIngest_CountFileRefs_Bad_EmptyBody(t *testing.T) { - assert.Equal(t, 0, countFileRefs("")) + core.AssertEqual( + t, + 0, + countFileRefs(""), + ) } func TestIngest_CountFileRefs_Bad_ShortBody(t *testing.T) { // Body too short to contain a valid reference - assert.Equal(t, 0, countFileRefs("`a`")) + core.AssertEqual( + t, + 0, + countFileRefs("`a`"), + ) } func TestIngest_CountFileRefs_Ugly_MalformedBackticks(t *testing.T) { // Unclosed backtick — should not panic or hang body := "Something `unclosed" - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { countFileRefs(body) }) } @@ -590,66 +620,94 @@ func TestIngest_CountFileRefs_Ugly_MalformedBackticks(t *testing.T) { func TestIngest_CountFileRefs_Ugly_LongRef(t *testing.T) { // Reference longer than 100 chars should not be counted (loop limit) longRef := "`" + strings.Repeat("a", 101) + ".go:1`" - assert.Equal(t, 0, countFileRefs(longRef)) + core.AssertEqual( + t, + 0, + countFileRefs(longRef), + ) } // --- modelVariant --- func TestQueue_ModelVariant_Good_WithModel(t *testing.T) { - assert.Equal(t, "gpt-5.4", modelVariant("codex:gpt-5.4")) - assert.Equal(t, "flash", modelVariant("gemini:flash")) - assert.Equal(t, "opus", modelVariant("claude:opus")) - assert.Equal(t, "haiku", modelVariant("claude:haiku")) + core.AssertEqual(t, "gpt-5.4", modelVariant("codex:gpt-5.4")) + core.AssertEqual(t, "flash", modelVariant("gemini:flash")) + core.AssertEqual(t, "opus", modelVariant("claude:opus")) + core.AssertEqual(t, "haiku", modelVariant("claude:haiku")) } func TestQueue_ModelVariant_Good_NoVariant(t *testing.T) { - assert.Equal(t, "", modelVariant("codex")) - assert.Equal(t, "", modelVariant("claude")) - assert.Equal(t, "", modelVariant("gemini")) + core.AssertEqual(t, "", modelVariant("codex")) + core.AssertEqual(t, "", modelVariant("claude")) + core.AssertEqual(t, "", modelVariant("gemini")) } func TestQueue_ModelVariant_Good_MultipleColons(t *testing.T) { // SplitN(2) only splits on first colon; rest is preserved as the model - assert.Equal(t, "gpt-5.3-codex-spark", modelVariant("codex:gpt-5.3-codex-spark")) + core.AssertEqual( + t, + "gpt-5.3-codex-spark", + modelVariant("codex:gpt-5.3-codex-spark"), + ) } func TestQueue_ModelVariant_Bad_EmptyString(t *testing.T) { - assert.Equal(t, "", modelVariant("")) + core.AssertEqual( + t, + "", + modelVariant(""), + ) } func TestQueue_ModelVariant_Ugly_ColonOnly(t *testing.T) { // Just a colon with no model name - assert.Equal(t, "", modelVariant(":")) + core.AssertEqual( + t, + "", + modelVariant(":"), + ) } // --- baseAgent --- func TestQueue_BaseAgent_Good_Variants(t *testing.T) { - assert.Equal(t, "gemini", baseAgent("gemini:flash")) - assert.Equal(t, "gemini", baseAgent("gemini:pro")) - assert.Equal(t, "claude", baseAgent("claude:haiku")) - assert.Equal(t, "codex", baseAgent("codex:gpt-5.4")) + core.AssertEqual(t, "gemini", baseAgent("gemini:flash")) + core.AssertEqual(t, "gemini", baseAgent("gemini:pro")) + core.AssertEqual(t, "claude", baseAgent("claude:haiku")) + core.AssertEqual(t, "codex", baseAgent("codex:gpt-5.4")) } func TestQueue_BaseAgent_Good_NoVariant(t *testing.T) { - assert.Equal(t, "codex", baseAgent("codex")) - assert.Equal(t, "claude", baseAgent("claude")) - assert.Equal(t, "gemini", baseAgent("gemini")) + core.AssertEqual(t, "codex", baseAgent("codex")) + core.AssertEqual(t, "claude", baseAgent("claude")) + core.AssertEqual(t, "gemini", baseAgent("gemini")) } func TestQueue_BaseAgent_Good_CodexSpark(t *testing.T) { // spark is codex, not a separate pool - assert.Equal(t, "codex", baseAgent("codex:gpt-5.3-codex-spark")) + core.AssertEqual( + t, + "codex", + baseAgent("codex:gpt-5.3-codex-spark"), + ) } func TestQueue_BaseAgent_Bad_EmptyString(t *testing.T) { // Empty string — SplitN returns [""], so first element is "" - assert.Equal(t, "", baseAgent("")) + core.AssertEqual( + t, + "", + baseAgent(""), + ) } func TestQueue_BaseAgent_Ugly_JustColon(t *testing.T) { // Just a colon — base is empty string before colon - assert.Equal(t, "", baseAgent(":model")) + core.AssertEqual( + t, + "", + baseAgent(":model"), + ) } // --- resolveWorkspace --- @@ -661,10 +719,10 @@ func TestHandlers_ResolveWorkspace_Good_ExistingDir(t *testing.T) { // Create the workspace directory structure workspaceName := "core/go-io/task-5" workspaceDir := core.JoinPath(root, "workspace", workspaceName) - require.True(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) result := resolveWorkspace(workspaceName) - assert.Equal(t, workspaceDir, result) + core.AssertEqual(t, workspaceDir, result) } func TestHandlers_ResolveWorkspace_Good_NestedPath(t *testing.T) { @@ -673,10 +731,10 @@ func TestHandlers_ResolveWorkspace_Good_NestedPath(t *testing.T) { workspaceName := "core/agent/pr-42" workspaceDir := core.JoinPath(root, "workspace", workspaceName) - require.True(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) result := resolveWorkspace(workspaceName) - assert.Equal(t, workspaceDir, result) + core.AssertEqual(t, workspaceDir, result) } func TestHandlers_ResolveWorkspace_Bad_NonExistentDir(t *testing.T) { @@ -684,7 +742,7 @@ func TestHandlers_ResolveWorkspace_Bad_NonExistentDir(t *testing.T) { setTestWorkspace(t, root) result := resolveWorkspace("core/go-io/task-999") - assert.Equal(t, "", result) + core.AssertEqual(t, "", result) } func TestHandlers_ResolveWorkspace_Bad_EmptyName(t *testing.T) { @@ -705,7 +763,7 @@ func TestHandlers_ResolveWorkspace_Ugly_PathTraversal(t *testing.T) { // Path traversal attempt should return "" (parent of workspace root won't be a workspace) result := resolveWorkspace("../../etc") - assert.Equal(t, "", result) + core.AssertEqual(t, "", result) } // --- findWorkspaceByPR --- @@ -715,15 +773,15 @@ func TestHandlers_FindWorkspaceByPR_Good_MatchesFlatLayout(t *testing.T) { setTestWorkspace(t, root) wsDir := core.JoinPath(root, "workspace", "task-10") - require.True(t, fs.EnsureDir(wsDir).OK) - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(wsDir).OK) + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Branch: "agent/fix-timeout", })) result := findWorkspaceByPR("go-io", "agent/fix-timeout") - assert.Equal(t, wsDir, result) + core.AssertEqual(t, wsDir, result) } func TestHandlers_FindWorkspaceByPR_Good_MatchesDeepLayout(t *testing.T) { @@ -731,15 +789,15 @@ func TestHandlers_FindWorkspaceByPR_Good_MatchesDeepLayout(t *testing.T) { setTestWorkspace(t, root) wsDir := core.JoinPath(root, "workspace", "core", "go-io", "task-15") - require.True(t, fs.EnsureDir(wsDir).OK) - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(wsDir).OK) + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "running", Repo: "go-io", Branch: "agent/add-metrics", })) result := findWorkspaceByPR("go-io", "agent/add-metrics") - assert.Equal(t, wsDir, result) + core.AssertEqual(t, wsDir, result) } func TestHandlers_FindWorkspaceByPR_Bad_NoMatch(t *testing.T) { @@ -747,15 +805,15 @@ func TestHandlers_FindWorkspaceByPR_Bad_NoMatch(t *testing.T) { setTestWorkspace(t, root) wsDir := core.JoinPath(root, "workspace", "task-99") - require.True(t, fs.EnsureDir(wsDir).OK) - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(wsDir).OK) + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-io", Branch: "agent/some-other-branch", })) result := findWorkspaceByPR("go-io", "agent/nonexistent-branch") - assert.Equal(t, "", result) + core.AssertEqual(t, "", result) } func TestHandlers_FindWorkspaceByPR_Bad_EmptyWorkspace(t *testing.T) { @@ -763,7 +821,7 @@ func TestHandlers_FindWorkspaceByPR_Bad_EmptyWorkspace(t *testing.T) { setTestWorkspace(t, root) // No workspaces at all result := findWorkspaceByPR("go-io", "agent/any-branch") - assert.Equal(t, "", result) + core.AssertEqual(t, "", result) } func TestHandlers_FindWorkspaceByPR_Bad_RepoDiffers(t *testing.T) { @@ -771,8 +829,8 @@ func TestHandlers_FindWorkspaceByPR_Bad_RepoDiffers(t *testing.T) { setTestWorkspace(t, root) wsDir := core.JoinPath(root, "workspace", "task-5") - require.True(t, fs.EnsureDir(wsDir).OK) - require.NoError(t, writeStatus(wsDir, &WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(wsDir).OK) + core.RequireNoError(t, writeStatus(wsDir, &WorkspaceStatus{ Status: "completed", Repo: "go-log", Branch: "agent/fix-formatter", @@ -780,7 +838,7 @@ func TestHandlers_FindWorkspaceByPR_Bad_RepoDiffers(t *testing.T) { // Same branch, different repo result := findWorkspaceByPR("go-io", "agent/fix-formatter") - assert.Equal(t, "", result) + core.AssertEqual(t, "", result) } func TestHandlers_FindWorkspaceByPR_Ugly_CorruptStatusFile(t *testing.T) { @@ -788,41 +846,51 @@ func TestHandlers_FindWorkspaceByPR_Ugly_CorruptStatusFile(t *testing.T) { setTestWorkspace(t, root) wsDir := core.JoinPath(root, "workspace", "corrupt-ws") - require.True(t, fs.EnsureDir(wsDir).OK) - require.True(t, fs.Write(core.JoinPath(wsDir, "status.json"), "not-valid-json{").OK) + core.RequireTrue(t, fs.EnsureDir(wsDir).OK) + core.RequireTrue(t, fs.Write(core.JoinPath(wsDir, "status.json"), "not-valid-json{").OK) // Should skip corrupt entries, not panic result := findWorkspaceByPR("go-io", "agent/any") - assert.Equal(t, "", result) + core.AssertEqual(t, "", result) } // --- extractPullRequestNumber --- func TestVerify_ExtractPullRequestNumber_Good_FullURL(t *testing.T) { - assert.Equal(t, 42, extractPullRequestNumber("https://forge.lthn.ai/core/agent/pulls/42")) - assert.Equal(t, 1, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/1")) - assert.Equal(t, 999, extractPullRequestNumber("https://forge.lthn.ai/core/go-log/pulls/999")) + core.AssertEqual(t, 42, extractPullRequestNumber("https://forge.lthn.ai/core/agent/pulls/42")) + core.AssertEqual(t, 1, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/1")) + core.AssertEqual(t, 999, extractPullRequestNumber("https://forge.lthn.ai/core/go-log/pulls/999")) } func TestVerify_ExtractPullRequestNumber_Good_NumberOnly(t *testing.T) { // If someone passes a bare number as a URL it should still work - assert.Equal(t, 7, extractPullRequestNumber("7")) + number := extractPullRequestNumber("7") + core.AssertEqual(t, 7, number) + core.AssertTrue(t, number > 0) } func TestVerify_ExtractPullRequestNumber_Bad_EmptyURL(t *testing.T) { - assert.Equal(t, 0, extractPullRequestNumber("")) + number := extractPullRequestNumber("") + core.AssertEqual(t, 0, number) + core.AssertFalse(t, number > 0) } func TestVerify_ExtractPullRequestNumber_Bad_TrailingSlash(t *testing.T) { // URL ending with slash has empty last segment - assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/")) + number := extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/") + core.AssertEqual(t, 0, number) + core.AssertFalse(t, number > 0) } func TestVerify_ExtractPullRequestNumber_Bad_NonNumericEnd(t *testing.T) { - assert.Equal(t, 0, extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/abc")) + number := extractPullRequestNumber("https://forge.lthn.ai/core/go-io/pulls/abc") + core.AssertEqual(t, 0, number) + core.AssertFalse(t, number > 0) } func TestVerify_ExtractPullRequestNumber_Ugly_JustSlashes(t *testing.T) { // All slashes — last segment is empty - assert.Equal(t, 0, extractPullRequestNumber("///")) + number := extractPullRequestNumber("///") + core.AssertEqual(t, 0, number) + core.AssertFalse(t, number > 0) } diff --git a/pkg/agentic/message.go b/pkg/agentic/message.go index 259caf16..35b7d5e7 100644 --- a/pkg/agentic/message.go +++ b/pkg/agentic/message.go @@ -7,8 +7,8 @@ import ( "slices" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/message_test.go b/pkg/agentic/message_test.go index 288f3ca1..0a327f08 100644 --- a/pkg/agentic/message_test.go +++ b/pkg/agentic/message_test.go @@ -7,10 +7,8 @@ import ( "testing" "time" + core "dappco.re/go" "dappco.re/go/agent/pkg/messages" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMessage_MessageSend_Good_PersistsAndReadsBack(t *testing.T) { @@ -26,45 +24,45 @@ func TestMessage_MessageSend_Good_PersistsAndReadsBack(t *testing.T) { core.Option{Key: "subject", Value: "Review"}, core.Option{Key: "content", Value: "Please check the prompt."}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(MessageSendOutput) - require.True(t, ok) - assert.Equal(t, "core/go-io/task-5", output.Message.Workspace) - assert.Equal(t, "codex", output.Message.FromAgent) - assert.Equal(t, "claude", output.Message.ToAgent) - assert.Equal(t, "Review", output.Message.Subject) - assert.Equal(t, "Please check the prompt.", output.Message.Content) - assert.NotEmpty(t, output.Message.ID) - assert.NotEmpty(t, output.Message.CreatedAt) + core.RequireTrue(t, ok) + core.AssertEqual(t, "core/go-io/task-5", output.Message.Workspace) + core.AssertEqual(t, "codex", output.Message.FromAgent) + core.AssertEqual(t, "claude", output.Message.ToAgent) + core.AssertEqual(t, "Review", output.Message.Subject) + core.AssertEqual(t, "Please check the prompt.", output.Message.Content) + core.AssertNotEmpty(t, output.Message.ID) + core.AssertNotEmpty(t, output.Message.CreatedAt) messageStorePath := messagePath("core/go-io/task-5") - assert.True(t, fs.Exists(messageStorePath)) + core.AssertTrue(t, fs.Exists(messageStorePath)) inboxResult := s.cmdMessageInbox(core.NewOptions( core.Option{Key: "_arg", Value: "core/go-io/task-5"}, core.Option{Key: "agent", Value: "claude"}, )) - require.True(t, inboxResult.OK) + core.RequireTrue(t, inboxResult.OK) inbox, ok := inboxResult.Value.(MessageListOutput) - require.True(t, ok) - assert.Equal(t, 1, inbox.Count) - require.Len(t, inbox.Messages, 1) - assert.Equal(t, output.Message.ID, inbox.Messages[0].ID) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, inbox.Count) + core.AssertLen(t, inbox.Messages, 1) + core.AssertEqual(t, output.Message.ID, inbox.Messages[0].ID) conversationResult := s.cmdMessageConversation(core.NewOptions( core.Option{Key: "_arg", Value: "core/go-io/task-5"}, core.Option{Key: "agent", Value: "codex"}, core.Option{Key: "with", Value: "claude"}, )) - require.True(t, conversationResult.OK) + core.RequireTrue(t, conversationResult.OK) conversation, ok := conversationResult.Value.(MessageListOutput) - require.True(t, ok) - assert.Equal(t, 1, conversation.Count) - require.Len(t, conversation.Messages, 1) - assert.Equal(t, output.Message.ID, conversation.Messages[0].ID) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, conversation.Count) + core.AssertLen(t, conversation.Messages, 1) + core.AssertEqual(t, output.Message.ID, conversation.Messages[0].ID) } func TestMessage_MessageInbox_Good_MarksReadAndEmitsCounts(t *testing.T) { @@ -92,35 +90,35 @@ func TestMessage_MessageInbox_Good_MarksReadAndEmitsCounts(t *testing.T) { core.Option{Key: "to_agent", Value: "claude"}, core.Option{Key: "content", Value: "Please review this."}, )) - require.True(t, sendResult.OK) + core.RequireTrue(t, sendResult.OK) inboxResult := s.handleMessageInbox(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: "core/go-io/task-5"}, core.Option{Key: "agent", Value: "claude"}, )) - require.True(t, inboxResult.OK) + core.RequireTrue(t, inboxResult.OK) inbox, ok := inboxResult.Value.(MessageListOutput) - require.True(t, ok) - assert.Equal(t, 1, inbox.New) - assert.Equal(t, 1, inbox.Count) - require.Len(t, inbox.Messages, 1) - assert.NotEmpty(t, inbox.Messages[0].ReadAt) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, inbox.New) + core.AssertEqual(t, 1, inbox.Count) + core.AssertLen(t, inbox.Messages, 1) + core.AssertNotEmpty(t, inbox.Messages[0].ReadAt) secondResult := s.handleMessageInbox(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: "core/go-io/task-5"}, core.Option{Key: "agent", Value: "claude"}, )) - require.True(t, secondResult.OK) + core.RequireTrue(t, secondResult.OK) secondInbox, ok := secondResult.Value.(MessageListOutput) - require.True(t, ok) - assert.Equal(t, 0, secondInbox.New) - assert.Len(t, inboxEvents, 2) - assert.Equal(t, 1, inboxEvents[0].New) - assert.Equal(t, 1, inboxEvents[0].Total) - assert.Equal(t, 0, inboxEvents[1].New) - assert.Equal(t, 1, inboxEvents[1].Total) + core.RequireTrue(t, ok) + core.AssertEqual(t, 0, secondInbox.New) + core.AssertLen(t, inboxEvents, 2) + core.AssertEqual(t, 1, inboxEvents[0].New) + core.AssertEqual(t, 1, inboxEvents[0].Total) + core.AssertEqual(t, 0, inboxEvents[1].New) + core.AssertEqual(t, 1, inboxEvents[1].Total) } func TestMessage_MessageSend_Bad_MissingRequiredFields(t *testing.T) { @@ -131,9 +129,9 @@ func TestMessage_MessageSend_Bad_MissingRequiredFields(t *testing.T) { core.Option{Key: "from", Value: "codex"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "required") } func TestMessage_MessageSend_Ugly_WhitespaceContent(t *testing.T) { @@ -146,9 +144,9 @@ func TestMessage_MessageSend_Ugly_WhitespaceContent(t *testing.T) { core.Option{Key: "content", Value: " "}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "required") } func TestMessage_MessageInbox_Good_NoMessages(t *testing.T) { @@ -162,11 +160,11 @@ func TestMessage_MessageInbox_Good_NoMessages(t *testing.T) { core.Option{Key: "agent", Value: "claude"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(MessageListOutput) - require.True(t, ok) - assert.Equal(t, 0, output.Count) - assert.Empty(t, output.Messages) + core.RequireTrue(t, ok) + core.AssertEqual(t, 0, output.Count) + core.AssertEmpty(t, output.Messages) } func TestMessage_MessageInbox_Bad_MissingRequiredFields(t *testing.T) { @@ -174,17 +172,17 @@ func TestMessage_MessageInbox_Bad_MissingRequiredFields(t *testing.T) { result := s.cmdMessageInbox(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "required") } func TestMessage_HandleMessageInbox_Ugly_CorruptStore(t *testing.T) { dir := t.TempDir() setTestWorkspace(t, dir) - require.True(t, fs.EnsureDir(messageRoot()).OK) - require.True(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK) + core.RequireTrue(t, fs.EnsureDir(messageRoot()).OK) + core.RequireTrue(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK) s := newTestPrep(t) @@ -193,9 +191,9 @@ func TestMessage_HandleMessageInbox_Ugly_CorruptStore(t *testing.T) { core.Option{Key: "agent", Value: "claude"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "failed to parse message store") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "failed to parse message store") } func TestMessage_MessageConversation_Good_NoMessages(t *testing.T) { @@ -210,11 +208,11 @@ func TestMessage_MessageConversation_Good_NoMessages(t *testing.T) { core.Option{Key: "with", Value: "claude"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(MessageListOutput) - require.True(t, ok) - assert.Equal(t, 0, output.Count) - assert.Empty(t, output.Messages) + core.RequireTrue(t, ok) + core.AssertEqual(t, 0, output.Count) + core.AssertEmpty(t, output.Messages) } func TestMessage_MessageConversation_Bad_MissingRequiredFields(t *testing.T) { @@ -222,17 +220,17 @@ func TestMessage_MessageConversation_Bad_MissingRequiredFields(t *testing.T) { result := s.cmdMessageConversation(core.NewOptions()) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "required") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "required") } func TestMessage_MessageConversation_Ugly_CorruptStore(t *testing.T) { dir := t.TempDir() setTestWorkspace(t, dir) - require.True(t, fs.EnsureDir(messageRoot()).OK) - require.True(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK) + core.RequireTrue(t, fs.EnsureDir(messageRoot()).OK) + core.RequireTrue(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK) s := newTestPrep(t) @@ -242,9 +240,9 @@ func TestMessage_MessageConversation_Ugly_CorruptStore(t *testing.T) { core.Option{Key: "with", Value: "claude"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "failed to parse message store") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "failed to parse message store") } func TestMessage_MessageInbox_Ugly_CorruptStore(t *testing.T) { @@ -252,15 +250,15 @@ func TestMessage_MessageInbox_Ugly_CorruptStore(t *testing.T) { setTestWorkspace(t, dir) s := newTestPrep(t) - require.True(t, fs.EnsureDir(messageRoot()).OK) - require.True(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK) + core.RequireTrue(t, fs.EnsureDir(messageRoot()).OK) + core.RequireTrue(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK) result := s.handleMessageInbox(context.Background(), core.NewOptions( core.Option{Key: "workspace", Value: "core/go-io/task-5"}, core.Option{Key: "agent", Value: "claude"}, )) - assert.False(t, result.OK) - require.Error(t, result.Value.(error)) - assert.Contains(t, result.Value.(error).Error(), "failed to parse message store") + core.AssertFalse(t, result.OK) + core.AssertError(t, result.Value.(error)) + core.AssertContains(t, result.Value.(error).Error(), "failed to parse message store") } diff --git a/pkg/agentic/mirror.go b/pkg/agentic/mirror.go index 9c39e3eb..cb236da0 100644 --- a/pkg/agentic/mirror.go +++ b/pkg/agentic/mirror.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/mirror_example_test.go b/pkg/agentic/mirror_example_test.go index e9d03f13..2e00749a 100644 --- a/pkg/agentic/mirror_example_test.go +++ b/pkg/agentic/mirror_example_test.go @@ -2,7 +2,7 @@ package agentic -import core "dappco.re/go/core" +import core "dappco.re/go" func ExampleMirrorInput() { input := MirrorInput{Repo: "go-io"} diff --git a/pkg/agentic/mirror_test.go b/pkg/agentic/mirror_test.go index dc9e3c89..7e43ff4e 100644 --- a/pkg/agentic/mirror_test.go +++ b/pkg/agentic/mirror_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) // initBareRepo creates a minimal git repo with one commit and returns its path. @@ -24,14 +22,16 @@ func initBareRepo(t *testing.T) string { run := func(args ...string) { t.Helper() r := testCore.Process().RunWithEnv(context.Background(), dir, gitEnv, args[0], args[1:]...) - require.True(t, r.OK, "cmd %v failed: %s", args, r.Value) + if !r.OK { + t.Fatalf("cmd %v failed: %v", args, r.Value) + } } run("git", "init", "-b", "main") run("git", "config", "user.name", "Test") run("git", "config", "user.email", "test@test.com") // Create a file and commit - require.True(t, fs.Write(core.JoinPath(dir, "README.md"), "# Test").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "README.md"), "# Test").OK) run("git", "add", "README.md") run("git", "commit", "-m", "initial commit") return dir @@ -44,30 +44,36 @@ func TestMirror_HasRemote_Good_OriginExists(t *testing.T) { // origin won't exist for a fresh repo, so add it testCore.Process().RunIn(context.Background(), dir, "git", "remote", "add", "origin", "https://example.com/repo.git") - assert.True(t, testPrep.hasRemote(dir, "origin")) + core.AssertTrue(t, testPrep.hasRemote(dir, "origin")) } func TestMirror_HasRemote_Good_CustomRemote(t *testing.T) { dir := initBareRepo(t) testCore.Process().RunIn(context.Background(), dir, "git", "remote", "add", "github", "https://github.com/test/repo.git") - assert.True(t, testPrep.hasRemote(dir, "github")) + core.AssertTrue(t, testPrep.hasRemote(dir, "github")) } func TestMirror_HasRemote_Bad_NoSuchRemote(t *testing.T) { dir := initBareRepo(t) - assert.False(t, testPrep.hasRemote(dir, "nonexistent")) + core.AssertFalse( + t, + testPrep.hasRemote(dir, "nonexistent"), + ) } func TestMirror_HasRemote_Bad_NotAGitRepo(t *testing.T) { dir := t.TempDir() // plain directory, no .git - assert.False(t, testPrep.hasRemote(dir, "origin")) + core.AssertFalse( + t, + testPrep.hasRemote(dir, "origin"), + ) } func TestMirror_HasRemote_Ugly_EmptyDir(t *testing.T) { // Empty dir defaults to cwd which may or may not be a repo. // Just ensure no panic. - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { testPrep.hasRemote("", "origin") }) } @@ -82,18 +88,20 @@ func TestMirror_CommitsAhead_Good_OneAhead(t *testing.T) { run := func(args ...string) { t.Helper() r := testCore.Process().RunWithEnv(context.Background(), dir, gitEnv, args[0], args[1:]...) - require.True(t, r.OK, "cmd %v failed: %s", args, r.Value) + if !r.OK { + t.Fatalf("cmd %v failed: %v", args, r.Value) + } } run("git", "branch", "base") // Add a commit on main - require.True(t, fs.Write(core.JoinPath(dir, "new.txt"), "data").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "new.txt"), "data").OK) run("git", "add", "new.txt") run("git", "commit", "-m", "second commit") ahead := testPrep.commitsAhead(dir, "base", "main") - assert.Equal(t, 1, ahead) + core.AssertEqual(t, 1, ahead) } func TestMirror_CommitsAhead_Good_ThreeAhead(t *testing.T) { @@ -102,43 +110,53 @@ func TestMirror_CommitsAhead_Good_ThreeAhead(t *testing.T) { run := func(args ...string) { t.Helper() r := testCore.Process().RunWithEnv(context.Background(), dir, gitEnv, args[0], args[1:]...) - require.True(t, r.OK, "cmd %v failed: %s", args, r.Value) + if !r.OK { + t.Fatalf("cmd %v failed: %v", args, r.Value) + } } run("git", "branch", "base") for i := 0; i < 3; i++ { name := core.JoinPath(dir, "file"+string(rune('a'+i))+".txt") - require.True(t, fs.Write(name, "content").OK) + core.RequireTrue(t, fs.Write(name, "content").OK) run("git", "add", ".") run("git", "commit", "-m", "commit "+string(rune('0'+i))) } ahead := testPrep.commitsAhead(dir, "base", "main") - assert.Equal(t, 3, ahead) + core.AssertEqual(t, 3, ahead) } func TestMirror_CommitsAhead_Good_ZeroAhead(t *testing.T) { dir := initBareRepo(t) // Same ref on both sides ahead := testPrep.commitsAhead(dir, "main", "main") - assert.Equal(t, 0, ahead) + core.AssertEqual(t, 0, ahead) } func TestMirror_CommitsAhead_Bad_InvalidRef(t *testing.T) { dir := initBareRepo(t) ahead := testPrep.commitsAhead(dir, "nonexistent-ref", "main") - assert.Equal(t, 0, ahead) + core.AssertEqual(t, 0, ahead) } func TestMirror_CommitsAhead_Bad_NotARepo(t *testing.T) { ahead := testPrep.commitsAhead(t.TempDir(), "main", "dev") - assert.Equal(t, 0, ahead) + core.AssertEqual( + t, + 0, + ahead, + ) } func TestMirror_CommitsAhead_Ugly_EmptyDir(t *testing.T) { ahead := testPrep.commitsAhead("", "a", "b") - assert.Equal(t, 0, ahead) + core.AssertEqual( + t, + 0, + ahead, + ) } // --- filesChanged --- @@ -149,17 +167,19 @@ func TestMirror_FilesChanged_Good_OneFile(t *testing.T) { run := func(args ...string) { t.Helper() r := testCore.Process().RunWithEnv(context.Background(), dir, gitEnv, args[0], args[1:]...) - require.True(t, r.OK, "cmd %v failed: %s", args, r.Value) + if !r.OK { + t.Fatalf("cmd %v failed: %v", args, r.Value) + } } run("git", "branch", "base") - require.True(t, fs.Write(core.JoinPath(dir, "changed.txt"), "new").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "changed.txt"), "new").OK) run("git", "add", "changed.txt") run("git", "commit", "-m", "add file") files := testPrep.filesChanged(dir, "base", "main") - assert.Equal(t, 1, files) + core.AssertEqual(t, 1, files) } func TestMirror_FilesChanged_Good_MultipleFiles(t *testing.T) { @@ -168,95 +188,149 @@ func TestMirror_FilesChanged_Good_MultipleFiles(t *testing.T) { run := func(args ...string) { t.Helper() r := testCore.Process().RunWithEnv(context.Background(), dir, gitEnv, args[0], args[1:]...) - require.True(t, r.OK, "cmd %v failed: %s", args, r.Value) + if !r.OK { + t.Fatalf("cmd %v failed: %v", args, r.Value) + } } run("git", "branch", "base") for _, name := range []string{"a.go", "b.go", "c.go"} { - require.True(t, fs.Write(core.JoinPath(dir, name), "package main").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, name), "package main").OK) } run("git", "add", ".") run("git", "commit", "-m", "add three files") files := testPrep.filesChanged(dir, "base", "main") - assert.Equal(t, 3, files) + core.AssertEqual(t, 3, files) } func TestMirror_FilesChanged_Good_NoChanges(t *testing.T) { dir := initBareRepo(t) files := testPrep.filesChanged(dir, "main", "main") - assert.Equal(t, 0, files) + core.AssertEqual(t, 0, files) } func TestMirror_FilesChanged_Bad_InvalidRef(t *testing.T) { dir := initBareRepo(t) files := testPrep.filesChanged(dir, "nonexistent", "main") - assert.Equal(t, 0, files) + core.AssertEqual(t, 0, files) } func TestMirror_FilesChanged_Bad_NotARepo(t *testing.T) { files := testPrep.filesChanged(t.TempDir(), "main", "dev") - assert.Equal(t, 0, files) + core.AssertEqual( + t, + 0, + files, + ) } func TestMirror_FilesChanged_Ugly_EmptyDir(t *testing.T) { files := testPrep.filesChanged("", "a", "b") - assert.Equal(t, 0, files) + core.AssertEqual( + t, + 0, + files, + ) } // --- extractJSONField (extending existing 91% coverage) --- func TestMirror_ExtractJSONField_Good_ArrayFirstItem(t *testing.T) { json := `[{"url":"https://github.com/test/pr/1","title":"Fix bug"}]` - assert.Equal(t, "https://github.com/test/pr/1", extractJSONField(json, "url")) + core.AssertEqual( + t, + "https://github.com/test/pr/1", + extractJSONField(json, "url"), + ) } func TestMirror_ExtractJSONField_Good_ObjectField(t *testing.T) { json := `{"name":"test-repo","status":"active"}` - assert.Equal(t, "test-repo", extractJSONField(json, "name")) + core.AssertEqual( + t, + "test-repo", + extractJSONField(json, "name"), + ) } func TestMirror_ExtractJSONField_Good_ArrayMultipleItems(t *testing.T) { json := `[{"id":"first"},{"id":"second"}]` // Should return the first match - assert.Equal(t, "first", extractJSONField(json, "id")) + core.AssertEqual( + t, + "first", + extractJSONField(json, "id"), + ) } func TestMirror_ExtractJSONField_Bad_EmptyJSON(t *testing.T) { - assert.Equal(t, "", extractJSONField("", "url")) + core.AssertEqual( + t, + "", + extractJSONField("", "url"), + ) } func TestMirror_ExtractJSONField_Bad_EmptyField(t *testing.T) { - assert.Equal(t, "", extractJSONField(`{"url":"test"}`, "")) + core.AssertEqual( + t, + "", + extractJSONField(`{"url":"test"}`, ""), + ) } func TestMirror_ExtractJSONField_Bad_FieldNotFound(t *testing.T) { json := `{"name":"test"}` - assert.Equal(t, "", extractJSONField(json, "missing")) + core.AssertEqual( + t, + "", + extractJSONField(json, "missing"), + ) } func TestMirror_ExtractJSONField_Bad_InvalidJSON(t *testing.T) { - assert.Equal(t, "", extractJSONField("not json at all", "url")) + core.AssertEqual( + t, + "", + extractJSONField("not json at all", "url"), + ) } func TestMirror_ExtractJSONField_Ugly_EmptyArray(t *testing.T) { - assert.Equal(t, "", extractJSONField("[]", "url")) + core.AssertEqual( + t, + "", + extractJSONField("[]", "url"), + ) } func TestMirror_ExtractJSONField_Ugly_EmptyObject(t *testing.T) { - assert.Equal(t, "", extractJSONField("{}", "url")) + core.AssertEqual( + t, + "", + extractJSONField("{}", "url"), + ) } func TestMirror_ExtractJSONField_Ugly_NumericValue(t *testing.T) { // Field exists but is not a string — should return "" json := `{"count":42}` - assert.Equal(t, "", extractJSONField(json, "count")) + core.AssertEqual( + t, + "", + extractJSONField(json, "count"), + ) } func TestMirror_ExtractJSONField_Ugly_NullValue(t *testing.T) { json := `{"url":null}` - assert.Equal(t, "", extractJSONField(json, "url")) + core.AssertEqual( + t, + "", + extractJSONField(json, "url"), + ) } // --- DefaultBranch --- @@ -265,14 +339,14 @@ func TestPaths_DefaultBranch_Good_MainBranch(t *testing.T) { dir := initBareRepo(t) // initBareRepo creates with -b main branch := testPrep.DefaultBranch(dir) - assert.Equal(t, "main", branch) + core.AssertEqual(t, "main", branch) } func TestPaths_DefaultBranch_Bad_NotARepo(t *testing.T) { dir := t.TempDir() // Falls back to "main" when detection fails branch := testPrep.DefaultBranch(dir) - assert.Equal(t, "main", branch) + core.AssertEqual(t, "main", branch) } // --- listLocalRepos --- @@ -287,38 +361,46 @@ func TestMirror_ListLocalRepos_Good_FindsRepos(t *testing.T) { } // Create a non-repo directory - require.True(t, fs.EnsureDir(core.JoinPath(base, "not-a-repo")).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(base, "not-a-repo")).OK) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{})} repos := s.listLocalRepos(base) - assert.Contains(t, repos, "repo-a") - assert.Contains(t, repos, "repo-b") - assert.NotContains(t, repos, "not-a-repo") + core.AssertContains(t, repos, "repo-a") + core.AssertContains(t, repos, "repo-b") + core.AssertNotContains(t, repos, "not-a-repo") } func TestMirror_ListLocalRepos_Bad_EmptyDir(t *testing.T) { base := t.TempDir() s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{})} repos := s.listLocalRepos(base) - assert.Empty(t, repos) + core.AssertEmpty(t, repos) } func TestMirror_ListLocalRepos_Bad_NonExistentDir(t *testing.T) { s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{})} repos := s.listLocalRepos("/nonexistent/path/that/doesnt/exist") - assert.Nil(t, repos) + core.AssertNil(t, repos) } // --- GitHubOrg --- func TestPaths_GitHubOrg_Good_Default(t *testing.T) { t.Setenv("GITHUB_ORG", "") - assert.Equal(t, "dAppCore", GitHubOrg()) + core.AssertEqual( + t, + "dAppCore", + GitHubOrg(), + ) } func TestPaths_GitHubOrg_Good_Custom(t *testing.T) { t.Setenv("GITHUB_ORG", "my-org") - assert.Equal(t, "my-org", GitHubOrg()) + core.AssertEqual( + t, + "my-org", + GitHubOrg(), + ) } // --- listLocalRepos Ugly --- @@ -334,18 +416,18 @@ func TestMirror_ListLocalRepos_Ugly(t *testing.T) { // Create non-git directories (no .git inside) for _, name := range []string{"plain-dir", "another-dir"} { - require.True(t, fs.EnsureDir(core.JoinPath(base, name)).OK) + core.RequireTrue(t, fs.EnsureDir(core.JoinPath(base, name)).OK) } // Create a regular file (not a directory) - require.True(t, fs.Write(core.JoinPath(base, "some-file.txt"), "hello").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(base, "some-file.txt"), "hello").OK) s := &PrepSubsystem{ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{})} repos := s.listLocalRepos(base) - assert.Contains(t, repos, "real-repo-a") - assert.Contains(t, repos, "real-repo-b") - assert.NotContains(t, repos, "plain-dir") - assert.NotContains(t, repos, "another-dir") - assert.NotContains(t, repos, "some-file.txt") - assert.Len(t, repos, 2) + core.AssertContains(t, repos, "real-repo-a") + core.AssertContains(t, repos, "real-repo-b") + core.AssertNotContains(t, repos, "plain-dir") + core.AssertNotContains(t, repos, "another-dir") + core.AssertNotContains(t, repos, "some-file.txt") + core.AssertLen(t, repos, 2) } diff --git a/pkg/agentic/paths.go b/pkg/agentic/paths.go index bc9f04f7..6eef1ce3 100644 --- a/pkg/agentic/paths.go +++ b/pkg/agentic/paths.go @@ -8,7 +8,7 @@ import ( "slices" "strconv" - core "dappco.re/go/core" + core "dappco.re/go" ) // fsEntry matches the fs.DirEntry methods used by workspace scanning. @@ -84,13 +84,13 @@ func CoreRoot() string { // home := agentic.HomeDir() func HomeDir() string { - if home := core.Env("CORE_HOME"); home != "" { + if home := core.Getenv("CORE_HOME"); home != "" { return home } - if home := core.Env("HOME"); home != "" { + if home := core.Getenv("HOME"); home != "" { return home } - return core.Env("DIR_HOME") + return core.Getenv("DIR_HOME") } func workspaceStatusPaths(workspaceRoot string) []string { diff --git a/pkg/agentic/paths_example_test.go b/pkg/agentic/paths_example_test.go index 4abbf9d7..abe94f1a 100644 --- a/pkg/agentic/paths_example_test.go +++ b/pkg/agentic/paths_example_test.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" ) func ExampleWorkspaceRoot() { diff --git a/pkg/agentic/paths_test.go b/pkg/agentic/paths_test.go index 44c7156d..4d9d02c4 100644 --- a/pkg/agentic/paths_test.go +++ b/pkg/agentic/paths_test.go @@ -7,46 +7,51 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - core "dappco.re/go/core" + core "dappco.re/go" ) func TestPaths_CoreRoot_Good_EnvVar(t *testing.T) { setTestWorkspace(t, "/tmp/test-core") - assert.Equal(t, "/tmp/test-core", CoreRoot()) + core.AssertEqual( + t, + "/tmp/test-core", + CoreRoot(), + ) } func TestPaths_CoreRoot_Good_Fallback(t *testing.T) { setTestWorkspace(t, "") home := HomeDir() - assert.Equal(t, home+"/Code/.core", CoreRoot()) + core.AssertEqual(t, home+"/Code/.core", CoreRoot()) } func TestPaths_CoreRoot_Good_CoreHome(t *testing.T) { setTestWorkspace(t, "") t.Setenv("CORE_HOME", "/tmp/core-home") - assert.Equal(t, "/tmp/core-home/Code/.core", CoreRoot()) + core.AssertEqual(t, "/tmp/core-home/Code/.core", CoreRoot()) } func TestPaths_HomeDir_Good_CoreHome(t *testing.T) { t.Setenv("CORE_HOME", "/tmp/core-home") t.Setenv("HOME", "/tmp/home") t.Setenv("DIR_HOME", "/tmp/dir-home") - assert.Equal(t, "/tmp/core-home", HomeDir()) + core.AssertEqual(t, "/tmp/core-home", HomeDir()) } func TestPaths_HomeDir_Good_HomeFallback(t *testing.T) { t.Setenv("CORE_HOME", "") t.Setenv("HOME", "/tmp/home") t.Setenv("DIR_HOME", "/tmp/dir-home") - assert.Equal(t, "/tmp/home", HomeDir()) + core.AssertEqual(t, "/tmp/home", HomeDir()) } func TestPaths_WorkspaceRoot_Good(t *testing.T) { setTestWorkspace(t, "/tmp/test-core") - assert.Equal(t, "/tmp/test-core/workspace", WorkspaceRoot()) + core.AssertEqual( + t, + "/tmp/test-core/workspace", + WorkspaceRoot(), + ) } func TestPaths_WorkspaceHelpers_Good(t *testing.T) { @@ -54,54 +59,70 @@ func TestPaths_WorkspaceHelpers_Good(t *testing.T) { wsDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-5") metaDir := WorkspaceMetaDir(wsDir) - assert.Equal(t, core.JoinPath(wsDir, "status.json"), WorkspaceStatusPath(wsDir)) - assert.Equal(t, core.JoinPath(wsDir, "repo"), WorkspaceRepoDir(wsDir)) - assert.Equal(t, core.JoinPath(wsDir, ".meta"), metaDir) - assert.Equal(t, core.JoinPath(wsDir, "repo", "BLOCKED.md"), WorkspaceBlockedPath(wsDir)) - assert.Equal(t, core.JoinPath(wsDir, "repo", "ANSWER.md"), WorkspaceAnswerPath(wsDir)) - assert.Equal(t, "core/go-io/task-5", WorkspaceName(wsDir)) + core.AssertEqual(t, core.JoinPath(wsDir, "status.json"), WorkspaceStatusPath(wsDir)) + core.AssertEqual(t, core.JoinPath(wsDir, "repo"), WorkspaceRepoDir(wsDir)) + core.AssertEqual(t, core.JoinPath(wsDir, ".meta"), metaDir) + core.AssertEqual(t, core.JoinPath(wsDir, "repo", "BLOCKED.md"), WorkspaceBlockedPath(wsDir)) + core.AssertEqual(t, core.JoinPath(wsDir, "repo", "ANSWER.md"), WorkspaceAnswerPath(wsDir)) + core.AssertEqual(t, "core/go-io/task-5", WorkspaceName(wsDir)) - assert.True(t, fs.EnsureDir(metaDir).OK) - assert.True(t, fs.Write(core.JoinPath(metaDir, "agent-codex.log"), "done").OK) - assert.Contains(t, WorkspaceLogFiles(wsDir), core.JoinPath(metaDir, "agent-codex.log")) + core.AssertTrue(t, fs.EnsureDir(metaDir).OK) + core.AssertTrue(t, fs.Write(core.JoinPath(metaDir, "agent-codex.log"), "done").OK) + core.AssertContains(t, WorkspaceLogFiles(wsDir), core.JoinPath(metaDir, "agent-codex.log")) } func TestPaths_WorkspaceHelpers_Good_BranchNameWithSlash(t *testing.T) { setTestWorkspace(t, "/tmp/test-core") wsDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "feature", "new-ui") - require.True(t, fs.EnsureDir(WorkspaceRepoDir(wsDir)).OK) - require.True(t, fs.EnsureDir(WorkspaceMetaDir(wsDir)).OK) - require.True(t, fs.Write(WorkspaceStatusPath(wsDir), "{}").OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceRepoDir(wsDir)).OK) + core.RequireTrue(t, fs.EnsureDir(WorkspaceMetaDir(wsDir)).OK) + core.RequireTrue(t, fs.Write(WorkspaceStatusPath(wsDir), "{}").OK) - assert.Equal(t, "core/go-io/feature/new-ui", WorkspaceName(wsDir)) - assert.Contains(t, WorkspaceStatusPaths(), WorkspaceStatusPath(wsDir)) + core.AssertEqual(t, "core/go-io/feature/new-ui", WorkspaceName(wsDir)) + core.AssertContains(t, WorkspaceStatusPaths(), WorkspaceStatusPath(wsDir)) } func TestPaths_PlansRoot_Good(t *testing.T) { setTestWorkspace(t, "/tmp/test-core") - assert.Equal(t, "/tmp/test-core/plans", PlansRoot()) + core.AssertEqual( + t, + "/tmp/test-core/plans", + PlansRoot(), + ) } func TestPaths_AgentName_Good_EnvVar(t *testing.T) { t.Setenv("AGENT_NAME", "clotho") - assert.Equal(t, "clotho", AgentName()) + core.AssertEqual( + t, + "clotho", + AgentName(), + ) } func TestPaths_AgentName_Good_Fallback(t *testing.T) { t.Setenv("AGENT_NAME", "") name := AgentName() - assert.True(t, name == "cladius" || name == "charon", "expected cladius or charon, got %s", name) + core.AssertTrue(t, name == "cladius" || name == "charon", "expected cladius or charon, got %s", name) } func TestPaths_GitHubOrg_Good_EnvVar(t *testing.T) { t.Setenv("GITHUB_ORG", "myorg") - assert.Equal(t, "myorg", GitHubOrg()) + core.AssertEqual( + t, + "myorg", + GitHubOrg(), + ) } func TestPaths_GitHubOrg_Good_Fallback(t *testing.T) { t.Setenv("GITHUB_ORG", "") - assert.Equal(t, "dAppCore", GitHubOrg()) + core.AssertEqual( + t, + "dAppCore", + GitHubOrg(), + ) } // --- DefaultBranch --- @@ -119,17 +140,17 @@ func TestPaths_DefaultBranch_Good(t *testing.T) { testCore.Process().RunIn(context.Background(), dir, "git", "commit", "-m", "init") branch := testPrep.DefaultBranch(dir) - assert.Equal(t, "main", branch) + core.AssertEqual(t, "main", branch) } func TestPaths_DefaultBranch_Bad(t *testing.T) { // Non-git directory — should return "main" (default) dir := t.TempDir() branch := testPrep.DefaultBranch(dir) - assert.Equal(t, "main", branch) + core.AssertEqual(t, "main", branch) } -func TestPaths_DefaultBranch_Ugly(t *testing.T) { +func TestDefaultBranchMaster_PrepSubsystem_DefaultBranch_Ugly(t *testing.T) { dir := t.TempDir() // Init git repo with "master" branch @@ -142,7 +163,7 @@ func TestPaths_DefaultBranch_Ugly(t *testing.T) { testCore.Process().RunIn(context.Background(), dir, "git", "commit", "-m", "init") branch := testPrep.DefaultBranch(dir) - assert.Equal(t, "master", branch) + core.AssertEqual(t, "master", branch) } // --- LocalFs Bad/Ugly --- @@ -150,12 +171,12 @@ func TestPaths_DefaultBranch_Ugly(t *testing.T) { func TestPaths_LocalFs_Bad_ReadNonExistent(t *testing.T) { f := LocalFs() r := f.Read("/tmp/nonexistent-path-" + strings.Repeat("x", 20) + "/file.txt") - assert.False(t, r.OK, "reading a non-existent file should fail") + core.AssertFalse(t, r.OK, "reading a non-existent file should fail") } func TestPaths_LocalFs_Ugly_EmptyPath(t *testing.T) { f := LocalFs() - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { f.Read("") }) } @@ -166,24 +187,24 @@ func TestPaths_WorkspaceRoot_Bad_EmptyEnv(t *testing.T) { setTestWorkspace(t, "") home := HomeDir() // Should fall back to ~/Code/.core/workspace - assert.Equal(t, home+"/Code/.core/workspace", WorkspaceRoot()) + core.AssertEqual(t, home+"/Code/.core/workspace", WorkspaceRoot()) } func TestPaths_WorkspaceHelpers_Bad(t *testing.T) { setTestWorkspace(t, "/tmp/test-core") - assert.Equal(t, "/status.json", WorkspaceStatusPath("")) - assert.Equal(t, "/repo", WorkspaceRepoDir("")) - assert.Equal(t, "/.meta", WorkspaceMetaDir("")) - assert.Equal(t, "workspace", WorkspaceName(WorkspaceRoot())) - assert.Empty(t, WorkspaceLogFiles("/tmp/missing-workspace")) + core.AssertEqual(t, "/status.json", WorkspaceStatusPath("")) + core.AssertEqual(t, "/repo", WorkspaceRepoDir("")) + core.AssertEqual(t, "/.meta", WorkspaceMetaDir("")) + core.AssertEqual(t, "workspace", WorkspaceName(WorkspaceRoot())) + core.AssertEmpty(t, WorkspaceLogFiles("/tmp/missing-workspace")) } func TestPaths_WorkspaceRoot_Ugly_TrailingSlash(t *testing.T) { setTestWorkspace(t, "/tmp/test-core/") // Verify it still constructs a valid path (JoinPath handles trailing slash) ws := WorkspaceRoot() - assert.NotEmpty(t, ws) - assert.Contains(t, ws, "workspace") + core.AssertNotEmpty(t, ws) + core.AssertContains(t, ws, "workspace") } func TestPaths_WorkspaceHelpers_Ugly(t *testing.T) { @@ -195,17 +216,17 @@ func TestPaths_WorkspaceHelpers_Ugly(t *testing.T) { deep := core.JoinPath(wsRoot, "core", "go-io", "task-12") ignored := core.JoinPath(wsRoot, "core", "go-io", "task-12", "extra") - assert.True(t, fs.EnsureDir(shallow).OK) - assert.True(t, fs.EnsureDir(deep).OK) - assert.True(t, fs.EnsureDir(ignored).OK) - assert.True(t, fs.Write(core.JoinPath(shallow, "status.json"), "{}").OK) - assert.True(t, fs.Write(core.JoinPath(deep, "status.json"), "{}").OK) - assert.True(t, fs.Write(core.JoinPath(ignored, "status.json"), "{}").OK) + core.AssertTrue(t, fs.EnsureDir(shallow).OK) + core.AssertTrue(t, fs.EnsureDir(deep).OK) + core.AssertTrue(t, fs.EnsureDir(ignored).OK) + core.AssertTrue(t, fs.Write(core.JoinPath(shallow, "status.json"), "{}").OK) + core.AssertTrue(t, fs.Write(core.JoinPath(deep, "status.json"), "{}").OK) + core.AssertTrue(t, fs.Write(core.JoinPath(ignored, "status.json"), "{}").OK) paths := WorkspaceStatusPaths() - assert.Contains(t, paths, core.JoinPath(shallow, "status.json")) - assert.Contains(t, paths, core.JoinPath(deep, "status.json")) - assert.NotContains(t, paths, core.JoinPath(ignored, "status.json")) + core.AssertContains(t, paths, core.JoinPath(shallow, "status.json")) + core.AssertContains(t, paths, core.JoinPath(deep, "status.json")) + core.AssertNotContains(t, paths, core.JoinPath(ignored, "status.json")) } // --- CoreRoot Bad/Ugly --- @@ -214,14 +235,14 @@ func TestPaths_CoreRoot_Bad_WhitespaceEnv(t *testing.T) { setTestWorkspace(t, " ") // Non-empty string (whitespace) will be used as-is root := CoreRoot() - assert.Equal(t, " ", root) + core.AssertEqual(t, " ", root) } func TestPaths_CoreRoot_Ugly_UnicodeEnv(t *testing.T) { setTestWorkspace(t, "/tmp/\u00e9\u00e0\u00fc") - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { root := CoreRoot() - assert.Equal(t, "/tmp/\u00e9\u00e0\u00fc", root) + core.AssertEqual(t, "/tmp/\u00e9\u00e0\u00fc", root) }) } @@ -230,12 +251,16 @@ func TestPaths_CoreRoot_Ugly_UnicodeEnv(t *testing.T) { func TestPaths_PlansRoot_Bad_EmptyEnv(t *testing.T) { setTestWorkspace(t, "") home := HomeDir() - assert.Equal(t, home+"/Code/.core/plans", PlansRoot()) + core.AssertEqual(t, home+"/Code/.core/plans", PlansRoot()) } func TestPaths_PlansRoot_Ugly_NestedPath(t *testing.T) { setTestWorkspace(t, "/a/b/c/d/e/f") - assert.Equal(t, "/a/b/c/d/e/f/plans", PlansRoot()) + core.AssertEqual( + t, + "/a/b/c/d/e/f/plans", + PlansRoot(), + ) } // --- AgentName Bad/Ugly --- @@ -243,14 +268,18 @@ func TestPaths_PlansRoot_Ugly_NestedPath(t *testing.T) { func TestPaths_AgentName_Bad_WhitespaceEnv(t *testing.T) { t.Setenv("AGENT_NAME", " ") // Whitespace is non-empty, so returned as-is - assert.Equal(t, " ", AgentName()) + core.AssertEqual( + t, + " ", + AgentName(), + ) } func TestPaths_AgentName_Ugly_UnicodeEnv(t *testing.T) { t.Setenv("AGENT_NAME", "\u00e9nchantr\u00efx") - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { name := AgentName() - assert.Equal(t, "\u00e9nchantr\u00efx", name) + core.AssertEqual(t, "\u00e9nchantr\u00efx", name) }) } @@ -258,55 +287,85 @@ func TestPaths_AgentName_Ugly_UnicodeEnv(t *testing.T) { func TestPaths_GitHubOrg_Bad_WhitespaceEnv(t *testing.T) { t.Setenv("GITHUB_ORG", " ") - assert.Equal(t, " ", GitHubOrg()) + core.AssertEqual( + t, + " ", + GitHubOrg(), + ) } func TestPaths_GitHubOrg_Ugly_SpecialChars(t *testing.T) { t.Setenv("GITHUB_ORG", "org/with/slashes") - assert.NotPanics(t, func() { + core.AssertNotPanics(t, func() { org := GitHubOrg() - assert.Equal(t, "org/with/slashes", org) + core.AssertEqual(t, "org/with/slashes", org) }) } // --- parseInt Bad/Ugly --- func TestPaths_ParseInt_Bad_EmptyString(t *testing.T) { - assert.Equal(t, 0, parseInt("")) + core.AssertEqual( + t, + 0, + parseInt(""), + ) } func TestPaths_ParseInt_Bad_NonNumeric(t *testing.T) { - assert.Equal(t, 0, parseInt("abc")) - assert.Equal(t, 0, parseInt("12.5")) - assert.Equal(t, 0, parseInt("0xff")) + core.AssertEqual(t, 0, parseInt("abc")) + core.AssertEqual(t, 0, parseInt("12.5")) + core.AssertEqual(t, 0, parseInt("0xff")) } func TestPaths_ParseInt_Bad_WhitespaceOnly(t *testing.T) { - assert.Equal(t, 0, parseInt(" ")) + core.AssertEqual( + t, + 0, + parseInt(" "), + ) } func TestPaths_ParseInt_Ugly_NegativeNumber(t *testing.T) { - assert.Equal(t, -42, parseInt("-42")) + core.AssertEqual( + t, + -42, + parseInt("-42"), + ) } func TestPaths_ParseInt_Ugly_VeryLargeNumber(t *testing.T) { - assert.Equal(t, 0, parseInt("99999999999999999999999")) + core.AssertEqual( + t, + 0, + parseInt("99999999999999999999999"), + ) } func TestPaths_ParseInt_Ugly_LeadingTrailingWhitespace(t *testing.T) { - assert.Equal(t, 42, parseInt(" 42 ")) + core.AssertEqual( + t, + 42, + parseInt(" 42 "), + ) } // --- fs (NewUnrestricted) Good --- func TestPaths_Fs_Good_Unrestricted(t *testing.T) { - assert.NotNil(t, fs, "package-level fs should be non-nil") - assert.IsType(t, &core.Fs{}, fs) + core.AssertNotNil( + t, + fs, + "package-level fs should be non-nil", + ) + assertIsType(t, &core.Fs{}, fs) } // --- parseInt Good --- func TestPaths_ParseInt_Good(t *testing.T) { - assert.Equal(t, 42, parseInt("42")) - assert.Equal(t, 0, parseInt("0")) + first := parseInt("42") + second := parseInt("0") + core.AssertEqual(t, 42, first) + core.AssertEqual(t, 0, second) } diff --git a/pkg/agentic/persist.go b/pkg/agentic/persist.go index 2aa72d2c..1083b52b 100644 --- a/pkg/agentic/persist.go +++ b/pkg/agentic/persist.go @@ -8,7 +8,7 @@ import ( "syscall" "time" - core "dappco.re/go/core" + core "dappco.re/go" ) const deadWorkerOnRestartQuestion = "dead worker on restart" @@ -26,7 +26,11 @@ func (s *PrepSubsystem) restorePersistedState(_ context.Context) core.Result { } s.workspaces = core.NewRegistry[*WorkspaceStatus]() - _ = s.stateStoreInstance() + if storeInstance := s.stateStoreInstance(); storeInstance == nil { + if err := s.stateStoreErr(); err != nil { + core.Warn("agentic.restorePersistedState: state store unavailable", "reason", err) + } + } restored := map[string]*WorkspaceStatus{} s.restoreRegistrySnapshot(restored) @@ -83,7 +87,11 @@ func (s *PrepSubsystem) flushPersistedState(_ context.Context) core.Result { return core.Result{OK: true} } - _ = s.stateStoreInstance() + if storeInstance := s.stateStoreInstance(); storeInstance == nil { + if err := s.stateStoreErr(); err != nil { + core.Warn("agentic.flushPersistedState: state store unavailable", "reason", err) + } + } registryKeep := map[string]struct{}{} queueKeep := map[string]struct{}{} @@ -151,7 +159,9 @@ func (s *PrepSubsystem) restoreQueueSnapshot(restored map[string]*WorkspaceStatu func (s *PrepSubsystem) restoreConcurrencySnapshot() { s.stateStoreRestore(stateConcurrencyGroup, func(_ string, value string) bool { var snapshot concurrencySnapshot - _ = core.JSONUnmarshalString(value, &snapshot) + if result := core.JSONUnmarshalString(value, &snapshot); !result.OK { + return true + } return true }) } @@ -245,7 +255,9 @@ func (s *PrepSubsystem) writePersistedWorkspaceStatus(workspaceDir string, works if workspaceStatus.UpdatedAt.IsZero() { workspaceStatus.UpdatedAt = time.Now().UTC() } - _ = fs.WriteAtomic(WorkspaceStatusPath(workspaceDir), core.JSONMarshalString(workspaceStatus)) + if writeResult := fs.WriteAtomic(WorkspaceStatusPath(workspaceDir), core.JSONMarshalString(workspaceStatus)); !writeResult.OK { + core.Warn("agentic.persist: failed to write persisted workspace status", "path", WorkspaceStatusPath(workspaceDir), "reason", writeResult.Value) + } } func cloneWorkspaceStatus(workspaceStatus *WorkspaceStatus) *WorkspaceStatus { diff --git a/pkg/agentic/persist_test.go b/pkg/agentic/persist_test.go index 5ea9ac3e..f638d8b1 100644 --- a/pkg/agentic/persist_test.go +++ b/pkg/agentic/persist_test.go @@ -7,9 +7,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPersist_OnStartup_Good_RestoresQueue(t *testing.T) { @@ -18,7 +16,7 @@ func TestPersist_OnStartup_Good_RestoresQueue(t *testing.T) { workspaceName := "core/go-io/task-restore" workspaceDir := core.JoinPath(root, "workspace", "core", "go-io", "task-restore") - require.True(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) subsystem := &PrepSubsystem{} defer subsystem.closeStateStore() @@ -38,24 +36,24 @@ func TestPersist_OnStartup_Good_RestoresQueue(t *testing.T) { }) result := subsystem.restorePersistedState(context.Background()) - require.True(t, result.OK) - assert.True(t, fs.IsFile(core.JoinPath(root, "db.duckdb"))) + core.RequireTrue(t, result.OK) + core.AssertTrue(t, fs.IsFile(core.JoinPath(root, "db.duckdb"))) registryResult := subsystem.Workspaces().Get(workspaceName) - require.True(t, registryResult.OK) + core.RequireTrue(t, registryResult.OK) workspaceStatus, ok := registryResult.Value.(*WorkspaceStatus) - require.True(t, ok) - assert.Equal(t, "queued", workspaceStatus.Status) - assert.Equal(t, "go-io", workspaceStatus.Repo) - assert.Equal(t, "core", workspaceStatus.Org) - assert.Equal(t, "agent/restore-queue", workspaceStatus.Branch) + core.RequireTrue(t, ok) + core.AssertEqual(t, "queued", workspaceStatus.Status) + core.AssertEqual(t, "go-io", workspaceStatus.Repo) + core.AssertEqual(t, "core", workspaceStatus.Org) + core.AssertEqual(t, "agent/restore-queue", workspaceStatus.Branch) statusResult := ReadStatusResult(workspaceDir) - require.True(t, statusResult.OK) + core.RequireTrue(t, statusResult.OK) restoredStatus, ok := workspaceStatusValue(statusResult) - require.True(t, ok) - assert.Equal(t, "queued", restoredStatus.Status) - assert.Equal(t, "go-io", restoredStatus.Repo) + core.RequireTrue(t, ok) + core.AssertEqual(t, "queued", restoredStatus.Status) + core.AssertEqual(t, "go-io", restoredStatus.Repo) } func TestPersist_OnStartup_Good_MarksDeadWorkers(t *testing.T) { @@ -64,7 +62,7 @@ func TestPersist_OnStartup_Good_MarksDeadWorkers(t *testing.T) { workspaceName := "core/go-io/task-dead" workspaceDir := core.JoinPath(root, "workspace", "core", "go-io", "task-dead") - require.True(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) subsystem := &PrepSubsystem{} defer subsystem.closeStateStore() @@ -87,23 +85,23 @@ func TestPersist_OnStartup_Good_MarksDeadWorkers(t *testing.T) { }) result := subsystem.restorePersistedState(context.Background()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) registryResult := subsystem.Workspaces().Get(workspaceName) - require.True(t, registryResult.OK) + core.RequireTrue(t, registryResult.OK) workspaceStatus, ok := registryResult.Value.(*WorkspaceStatus) - require.True(t, ok) - assert.Equal(t, "failed", workspaceStatus.Status) - assert.Equal(t, deadWorkerOnRestartQuestion, workspaceStatus.Question) - assert.Zero(t, workspaceStatus.PID) - assert.Empty(t, workspaceStatus.ProcessID) + core.RequireTrue(t, ok) + core.AssertEqual(t, "failed", workspaceStatus.Status) + core.AssertEqual(t, deadWorkerOnRestartQuestion, workspaceStatus.Question) + assertZero(t, workspaceStatus.PID) + core.AssertEmpty(t, workspaceStatus.ProcessID) statusResult := ReadStatusResult(workspaceDir) - require.True(t, statusResult.OK) + core.RequireTrue(t, statusResult.OK) restoredStatus, ok := workspaceStatusValue(statusResult) - require.True(t, ok) - assert.Equal(t, "failed", restoredStatus.Status) - assert.Equal(t, deadWorkerOnRestartQuestion, restoredStatus.Question) + core.RequireTrue(t, ok) + core.AssertEqual(t, "failed", restoredStatus.Status) + core.AssertEqual(t, deadWorkerOnRestartQuestion, restoredStatus.Question) } func TestPersist_OnShutdown_Good_PersistsQueue(t *testing.T) { @@ -143,24 +141,24 @@ func TestPersist_OnShutdown_Good_PersistsQueue(t *testing.T) { }) result := subsystem.OnShutdown(context.Background()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) replay := &PrepSubsystem{} defer replay.closeStateStore() queueValue, ok := replay.stateStoreGet(stateQueueGroup, "core/go-io/task-queue") - require.True(t, ok) + core.RequireTrue(t, ok) var entry queueEntry - require.True(t, core.JSONUnmarshalString(queueValue, &entry).OK) - assert.Equal(t, "go-io", entry.Repo) - assert.Equal(t, "agent/persist-queue", entry.Branch) + core.RequireTrue(t, core.JSONUnmarshalString(queueValue, &entry).OK) + core.AssertEqual(t, "go-io", entry.Repo) + core.AssertEqual(t, "agent/persist-queue", entry.Branch) registryValue, ok := replay.stateStoreGet(stateRegistryGroup, "core/go-store/task-running") - require.True(t, ok) + core.RequireTrue(t, ok) var stored WorkspaceStatus - require.True(t, core.JSONUnmarshalString(registryValue, &stored).OK) - assert.Equal(t, "running", stored.Status) - assert.Equal(t, "go-store", stored.Repo) + core.RequireTrue(t, core.JSONUnmarshalString(registryValue, &stored).OK) + core.AssertEqual(t, "running", stored.Status) + core.AssertEqual(t, "go-store", stored.Repo) } func TestPersist_OnStartup_Bad_IgnoresInvalidStorePayload(t *testing.T) { @@ -169,7 +167,7 @@ func TestPersist_OnStartup_Bad_IgnoresInvalidStorePayload(t *testing.T) { validWorkspace := "core/go-io/task-valid" validWorkspaceDir := core.JoinPath(root, "workspace", "core", "go-io", "task-valid") - require.True(t, fs.EnsureDir(validWorkspaceDir).OK) + core.RequireTrue(t, fs.EnsureDir(validWorkspaceDir).OK) subsystem := &PrepSubsystem{} defer subsystem.closeStateStore() @@ -178,7 +176,7 @@ func TestPersist_OnStartup_Bad_IgnoresInvalidStorePayload(t *testing.T) { t.Skip("go-store unavailable on this platform — RFC §15.6 graceful degradation") } - require.NoError(t, storeInstance.Set(stateRegistryGroup, "broken", "{")) + core.RequireNoError(t, storeInstance.Set(stateRegistryGroup, "broken", "{")) subsystem.stateStoreSet(stateQueueGroup, validWorkspace, queueEntry{ Repo: "go-io", Org: "core", @@ -189,9 +187,9 @@ func TestPersist_OnStartup_Bad_IgnoresInvalidStorePayload(t *testing.T) { }) result := subsystem.restorePersistedState(context.Background()) - require.True(t, result.OK) - assert.False(t, subsystem.Workspaces().Get("broken").OK) - assert.True(t, subsystem.Workspaces().Get(validWorkspace).OK) + core.RequireTrue(t, result.OK) + core.AssertFalse(t, subsystem.Workspaces().Get("broken").OK) + core.AssertTrue(t, subsystem.Workspaces().Get(validWorkspace).OK) } func TestPersist_OnStartup_Ugly_CleansCompletedOrphanedWorkspace(t *testing.T) { @@ -200,8 +198,8 @@ func TestPersist_OnStartup_Ugly_CleansCompletedOrphanedWorkspace(t *testing.T) { workspaceName := "core/go-io/task-completed" workspaceDir := core.JoinPath(root, "workspace", "core", "go-io", "task-completed") - require.True(t, fs.EnsureDir(workspaceDir).OK) - require.True(t, fs.WriteAtomic(WorkspaceStatusPath(workspaceDir), core.JSONMarshalString(WorkspaceStatus{ + core.RequireTrue(t, fs.EnsureDir(workspaceDir).OK) + core.RequireTrue(t, fs.WriteAtomic(WorkspaceStatusPath(workspaceDir), core.JSONMarshalString(WorkspaceStatus{ Status: "completed", Agent: "codex:gpt-5.4", Repo: "go-io", @@ -232,8 +230,8 @@ func TestPersist_OnStartup_Ugly_CleansCompletedOrphanedWorkspace(t *testing.T) { }) result := subsystem.restorePersistedState(context.Background()) - require.True(t, result.OK) - assert.False(t, fs.IsDir(workspaceDir)) + core.RequireTrue(t, result.OK) + core.AssertFalse(t, fs.IsDir(workspaceDir)) } func setPersistTestWorkspace(t *testing.T, root string) { diff --git a/pkg/agentic/phase.go b/pkg/agentic/phase.go index b73c3952..7eaf8e5e 100644 --- a/pkg/agentic/phase.go +++ b/pkg/agentic/phase.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/phase_test.go b/pkg/agentic/phase_test.go index bda1ec3a..bd3b94af 100644 --- a/pkg/agentic/phase_test.go +++ b/pkg/agentic/phase_test.go @@ -6,8 +6,7 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPhase_PhaseGet_Good(t *testing.T) { @@ -20,18 +19,18 @@ func TestPhase_PhaseGet_Good(t *testing.T) { Description: "Read phase", Phases: []Phase{{Number: 1, Name: "Setup"}}, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) _, output, err := s.phaseGet(context.Background(), nil, PhaseGetInput{ PlanSlug: plan.Slug, PhaseOrder: 1, }) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Equal(t, "Setup", output.Phase.Name) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "Setup", output.Phase.Name) } func TestPhase_PhaseUpdateStatus_Bad_InvalidStatus(t *testing.T) { @@ -41,8 +40,8 @@ func TestPhase_PhaseUpdateStatus_Bad_InvalidStatus(t *testing.T) { PhaseOrder: 1, Status: "invalid", }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid status") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "invalid status") } func TestPhase_PhaseAddCheckpoint_Ugly_AppendsCheckpoint(t *testing.T) { @@ -55,18 +54,18 @@ func TestPhase_PhaseAddCheckpoint_Ugly_AppendsCheckpoint(t *testing.T) { Description: "Append checkpoint", Phases: []Phase{{Number: 1, Name: "Setup"}}, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) _, output, err := s.phaseAddCheckpoint(context.Background(), nil, PhaseCheckpointInput{ PlanSlug: plan.Slug, PhaseOrder: 1, Note: "Build passes", }) - require.NoError(t, err) - assert.True(t, output.Success) - require.Len(t, output.Phase.Checkpoints, 1) - assert.Equal(t, "Build passes", output.Phase.Checkpoints[0].Note) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertLen(t, output.Phase.Checkpoints, 1) + core.AssertEqual(t, "Build passes", output.Phase.Checkpoints[0].Note) } diff --git a/pkg/agentic/pid.go b/pkg/agentic/pid.go index 9fe1074e..df6bbc6f 100644 --- a/pkg/agentic/pid.go +++ b/pkg/agentic/pid.go @@ -3,7 +3,7 @@ package agentic import ( - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/process" ) diff --git a/pkg/agentic/pid_example_test.go b/pkg/agentic/pid_example_test.go index 8428c4ee..dffa58e0 100644 --- a/pkg/agentic/pid_example_test.go +++ b/pkg/agentic/pid_example_test.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" "dappco.re/go/process" ) diff --git a/pkg/agentic/pid_test.go b/pkg/agentic/pid_test.go index 3bf4f701..da2d53ee 100644 --- a/pkg/agentic/pid_test.go +++ b/pkg/agentic/pid_test.go @@ -8,8 +8,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" + core "dappco.re/go" ) // testPrep is the package-level PrepSubsystem for tests that need process execution. @@ -20,6 +19,18 @@ var testCore *core.Core // TestMain sets up a PrepSubsystem with go-process registered for all tests in the package. func TestMain(m *testing.M) { + testRoot, err := os.MkdirTemp("", "agentic-tests-*") + if err != nil { + panic(err) + } + homeDir := core.JoinPath(testRoot, "home") + _ = os.MkdirAll(homeDir, 0o755) + _ = os.MkdirAll(core.JoinPath(homeDir, "Code", ".core"), 0o755) + + _ = os.Setenv("CORE_BRAIN_INSECURE", "true") + _ = os.Setenv("CORE_HOME", homeDir) + _ = os.Setenv("HOME", homeDir) + _ = os.Setenv("DIR_HOME", homeDir) testCore = core.New( core.WithService(ProcessRegister), ) @@ -55,16 +66,20 @@ func TestPid_ProcessAlive_Good(t *testing.T) { proc := startManagedProcess(t, testCore) pid := proc.Info().PID - assert.True(t, ProcessAlive(testCore, proc.ID, pid)) - assert.True(t, ProcessAlive(testCore, "", pid)) + core.AssertTrue(t, ProcessAlive(testCore, proc.ID, pid)) + core.AssertTrue(t, ProcessAlive(testCore, "", pid)) } func TestPid_ProcessAlive_Bad(t *testing.T) { - assert.False(t, ProcessAlive(testCore, "", 999999)) + alive := ProcessAlive(testCore, "", 999999) + core.AssertFalse(t, alive) + core.AssertEqual(t, false, alive) } func TestPid_ProcessAlive_Ugly(t *testing.T) { - assert.False(t, ProcessAlive(nil, "", 0)) + alive := ProcessAlive(nil, "", 0) + core.AssertFalse(t, alive) + core.AssertEqual(t, false, alive) } // --- ProcessTerminate --- @@ -73,7 +88,7 @@ func TestPid_ProcessTerminate_Good(t *testing.T) { proc := startManagedProcess(t, testCore) pid := proc.Info().PID - assert.True(t, ProcessTerminate(testCore, proc.ID, pid)) + core.AssertTrue(t, ProcessTerminate(testCore, proc.ID, pid)) select { case <-proc.Done(): @@ -81,13 +96,17 @@ func TestPid_ProcessTerminate_Good(t *testing.T) { t.Fatal("ProcessTerminate did not stop the process") } - assert.False(t, ProcessAlive(testCore, proc.ID, pid)) + core.AssertFalse(t, ProcessAlive(testCore, proc.ID, pid)) } func TestPid_ProcessTerminate_Bad(t *testing.T) { - assert.False(t, ProcessTerminate(testCore, "", 999999)) + terminated := ProcessTerminate(testCore, "", 999999) + core.AssertFalse(t, terminated) + core.AssertEqual(t, false, terminated) } func TestPid_ProcessTerminate_Ugly(t *testing.T) { - assert.False(t, ProcessTerminate(nil, "", 0)) + terminated := ProcessTerminate(nil, "", 0) + core.AssertFalse(t, terminated) + core.AssertEqual(t, false, terminated) } diff --git a/pkg/agentic/pipeline_audit.go b/pkg/agentic/pipeline_audit.go index b5f47784..6f7c8851 100644 --- a/pkg/agentic/pipeline_audit.go +++ b/pkg/agentic/pipeline_audit.go @@ -6,7 +6,7 @@ import ( "context" "regexp" - core "dappco.re/go/core" + core "dappco.re/go" ) var pipelineBulletPattern = regexp.MustCompile(`^\s*(?:[-*]|\d+\.)\s+(.*)$`) diff --git a/pkg/agentic/pipeline_audit_test.go b/pkg/agentic/pipeline_audit_test.go index 12adf689..f9658657 100644 --- a/pkg/agentic/pipeline_audit_test.go +++ b/pkg/agentic/pipeline_audit_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineAudit_Good_CreatesImplementationIssuesAndClosesAudit(t *testing.T) { @@ -25,14 +23,14 @@ func TestPipelineAudit_Good_CreatesImplementationIssuesAndClosesAudit(t *testing s, _ := testPrepWithCore(t, srv) output, err := s.pipelineAudit(context.Background(), PipelineAuditInput{Org: "core", Repo: "go-io"}) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Len(t, output.Audits, 1) - assert.Len(t, output.Created, 3) - assert.Equal(t, []int{1}, output.Closed) - assert.Equal(t, "closed", repo.Issues[1].State) - assert.Len(t, repo.Comments[1], 1) - assert.Contains(t, repo.Comments[1][0], "Implementation issues created") + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertLen(t, output.Audits, 1) + core.AssertLen(t, output.Created, 3) + core.AssertEqual(t, []int{1}, output.Closed) + core.AssertEqual(t, "closed", repo.Issues[1].State) + core.AssertLen(t, repo.Comments[1], 1) + core.AssertContains(t, repo.Comments[1][0], "Implementation issues created") } func TestPipelineAudit_Bad_MissingRepo(t *testing.T) { @@ -40,10 +38,10 @@ func TestPipelineAudit_Bad_MissingRepo(t *testing.T) { result := s.cmdPipelineAudit(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "repo is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "repo is required") } func TestPipelineAudit_Ugly_DeduplicatesExistingImplementationIssue(t *testing.T) { @@ -67,8 +65,8 @@ func TestPipelineAudit_Ugly_DeduplicatesExistingImplementationIssue(t *testing.T s, _ := testPrepWithCore(t, srv) output, err := s.pipelineAudit(context.Background(), PipelineAuditInput{Org: "core", Repo: "go-io"}) - require.NoError(t, err) - assert.Len(t, output.Existing, 1) - assert.Len(t, output.Created, 1) - assert.Equal(t, 2, output.Existing[0].Number) + core.RequireNoError(t, err) + core.AssertLen(t, output.Existing, 1) + core.AssertLen(t, output.Created, 1) + core.AssertEqual(t, 2, output.Existing[0].Number) } diff --git a/pkg/agentic/pipeline_budget.go b/pkg/agentic/pipeline_budget.go index f575e2fc..18541f2b 100644 --- a/pkg/agentic/pipeline_budget.go +++ b/pkg/agentic/pipeline_budget.go @@ -6,7 +6,7 @@ import ( "sort" "time" - core "dappco.re/go/core" + core "dappco.re/go" ) const pipelineBudgetStoreGroup = "pipeline_budget_dispatch" diff --git a/pkg/agentic/pipeline_budget_test.go b/pkg/agentic/pipeline_budget_test.go index 2585e16d..536731cb 100644 --- a/pkg/agentic/pipeline_budget_test.go +++ b/pkg/agentic/pipeline_budget_test.go @@ -6,9 +6,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineBudget_Good_RootHelp(t *testing.T) { @@ -16,16 +14,16 @@ func TestPipelineBudget_Good_RootHelp(t *testing.T) { output := captureStdout(t, func() { result := s.cmdPipelineBudget(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "core-agent pipeline/budget/plan") - assert.Contains(t, output, "core-agent pipeline/budget/log") + core.AssertContains(t, output, "core-agent pipeline/budget/plan") + core.AssertContains(t, output, "core-agent pipeline/budget/log") } func TestPipelineBudget_Good_PlanShowsRemainingBudgetByPool(t *testing.T) { s, _ := testPrepWithCore(t, nil) - require.True(t, fs.Write(core.JoinPath(CoreRoot(), "agents.yaml"), core.Concat( + core.RequireTrue(t, fs.Write(core.JoinPath(CoreRoot(), "agents.yaml"), core.Concat( "version: 1\n", "concurrency:\n", " codex: 1\n", @@ -41,22 +39,22 @@ func TestPipelineBudget_Good_PlanShowsRemainingBudgetByPool(t *testing.T) { core.Option{Key: "agent", Value: "codex"}, core.Option{Key: "model", Value: "gpt-5.4"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) var rows []pipelineBudgetPlanRow output := captureStdout(t, func() { plan := s.cmdPipelineBudgetPlan(core.NewOptions()) - require.True(t, plan.OK) + core.RequireTrue(t, plan.OK) var ok bool rows, ok = plan.Value.([]pipelineBudgetPlanRow) - require.True(t, ok) + core.RequireTrue(t, ok) }) - require.Len(t, rows, 1) - assert.Contains(t, output, "codex") - assert.Equal(t, 1, rows[0].UsedToday) - assert.Equal(t, "3", rows[0].DailyLimit) - assert.Equal(t, "2", rows[0].Remaining) + core.AssertLen(t, rows, 1) + core.AssertContains(t, output, "codex") + core.AssertEqual(t, 1, rows[0].UsedToday) + core.AssertEqual(t, "3", rows[0].DailyLimit) + core.AssertEqual(t, "2", rows[0].Remaining) } func TestPipelineBudget_Good_LogAppendsJournal(t *testing.T) { @@ -73,13 +71,13 @@ func TestPipelineBudget_Good_LogAppendsJournal(t *testing.T) { core.Option{Key: "status", Value: "queued"}, )) - require.True(t, first.OK) - require.True(t, second.OK) + core.RequireTrue(t, first.OK) + core.RequireTrue(t, second.OK) lines := readJSONLLines(pipelineBudgetJournalPath()) - require.Len(t, lines, 2) - assert.Contains(t, lines[0], `"repo":"go-io"`) - assert.Contains(t, lines[1], `"repo":"go-log"`) + core.AssertLen(t, lines, 2) + core.AssertContains(t, lines[0], `"repo":"go-io"`) + core.AssertContains(t, lines[1], `"repo":"go-log"`) } func TestPipelineBudget_Bad_LogRequiresRepoAndAgent(t *testing.T) { @@ -89,15 +87,15 @@ func TestPipelineBudget_Bad_LogRequiresRepoAndAgent(t *testing.T) { core.Option{Key: "repo", Value: "go-io"}, )) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "repo and agent are required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "repo and agent are required") } func TestPipelineBudget_Ugly_PlanSkipsCorruptJournalRows(t *testing.T) { s, _ := testPrepWithCore(t, nil) - require.True(t, fs.Write(core.JoinPath(CoreRoot(), "agents.yaml"), core.Concat( + core.RequireTrue(t, fs.Write(core.JoinPath(CoreRoot(), "agents.yaml"), core.Concat( "version: 1\n", "rates:\n", " codex:\n", @@ -113,14 +111,14 @@ func TestPipelineBudget_Ugly_PlanSkipsCorruptJournalRows(t *testing.T) { Model: "gpt-5.4", Status: "started", } - require.NoError(t, ensureParentDir(pipelineBudgetJournalPath())) - require.True(t, fs.WriteAtomic(pipelineBudgetJournalPath(), core.Concat( + core.RequireNoError(t, ensureParentDir(pipelineBudgetJournalPath())) + core.RequireTrue(t, fs.WriteAtomic(pipelineBudgetJournalPath(), core.Concat( "{not-json}\n", core.JSONMarshalString(valid), "\n", )).OK) rows := s.pipelineBudgetPlanRows(time.Now().UTC()) - require.Len(t, rows, 1) - assert.Equal(t, 1, rows[0].UsedToday) + core.AssertLen(t, rows, 1) + core.AssertEqual(t, 1, rows[0].UsedToday) } diff --git a/pkg/agentic/pipeline_commands.go b/pkg/agentic/pipeline_commands.go index 10829f8a..d2388dae 100644 --- a/pkg/agentic/pipeline_commands.go +++ b/pkg/agentic/pipeline_commands.go @@ -6,7 +6,7 @@ import ( "regexp" "unicode" - core "dappco.re/go/core" + core "dappco.re/go" ) var pipelineNumberPattern = regexp.MustCompile(`^[0-9]+$`) diff --git a/pkg/agentic/pipeline_commands_test.go b/pkg/agentic/pipeline_commands_test.go index b4d20620..010f997b 100644 --- a/pkg/agentic/pipeline_commands_test.go +++ b/pkg/agentic/pipeline_commands_test.go @@ -8,9 +8,7 @@ import ( "sort" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineCommands_RegisterPipelineCommands_Good(t *testing.T) { @@ -19,27 +17,27 @@ func TestPipelineCommands_RegisterPipelineCommands_Good(t *testing.T) { s.registerPipelineCommands() commands := c.Commands() - assert.Contains(t, commands, "pipeline") - assert.Contains(t, commands, "pipeline/audit") - assert.Contains(t, commands, "pipeline/epic") - assert.Contains(t, commands, "pipeline/epic/create") - assert.Contains(t, commands, "pipeline/epic/run") - assert.Contains(t, commands, "pipeline/epic/status") - assert.Contains(t, commands, "pipeline/epic/sync") - assert.Contains(t, commands, "pipeline/monitor") - assert.Contains(t, commands, "pipeline/fix") - assert.Contains(t, commands, "pipeline/fix/reviews") - assert.Contains(t, commands, "pipeline/fix/conflicts") - assert.Contains(t, commands, "pipeline/fix/format") - assert.Contains(t, commands, "pipeline/fix/threads") - assert.Contains(t, commands, "pipeline/onboard") - assert.Contains(t, commands, "pipeline/budget") - assert.Contains(t, commands, "pipeline/budget/plan") - assert.Contains(t, commands, "pipeline/budget/log") - assert.Contains(t, commands, "pipeline/training") - assert.Contains(t, commands, "pipeline/training/capture") - assert.Contains(t, commands, "pipeline/training/stats") - assert.Contains(t, commands, "pipeline/training/export") + core.AssertContains(t, commands, "pipeline") + core.AssertContains(t, commands, "pipeline/audit") + core.AssertContains(t, commands, "pipeline/epic") + core.AssertContains(t, commands, "pipeline/epic/create") + core.AssertContains(t, commands, "pipeline/epic/run") + core.AssertContains(t, commands, "pipeline/epic/status") + core.AssertContains(t, commands, "pipeline/epic/sync") + core.AssertContains(t, commands, "pipeline/monitor") + core.AssertContains(t, commands, "pipeline/fix") + core.AssertContains(t, commands, "pipeline/fix/reviews") + core.AssertContains(t, commands, "pipeline/fix/conflicts") + core.AssertContains(t, commands, "pipeline/fix/format") + core.AssertContains(t, commands, "pipeline/fix/threads") + core.AssertContains(t, commands, "pipeline/onboard") + core.AssertContains(t, commands, "pipeline/budget") + core.AssertContains(t, commands, "pipeline/budget/plan") + core.AssertContains(t, commands, "pipeline/budget/log") + core.AssertContains(t, commands, "pipeline/training") + core.AssertContains(t, commands, "pipeline/training/capture") + core.AssertContains(t, commands, "pipeline/training/stats") + core.AssertContains(t, commands, "pipeline/training/export") } func TestPipelineCommands_CmdPipeline_Good_Help(t *testing.T) { @@ -47,12 +45,12 @@ func TestPipelineCommands_CmdPipeline_Good_Help(t *testing.T) { output := captureStdout(t, func() { result := s.cmdPipeline(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "core-agent pipeline/audit ") - assert.Contains(t, output, "core-agent pipeline/epic/create ") - assert.Contains(t, output, "core-agent pipeline/onboard ") + core.AssertContains(t, output, "core-agent pipeline/audit ") + core.AssertContains(t, output, "core-agent pipeline/epic/create ") + core.AssertContains(t, output, "core-agent pipeline/onboard ") } func TestPipelineCommands_CmdPipeline_Bad_UnknownCommand(t *testing.T) { @@ -60,10 +58,10 @@ func TestPipelineCommands_CmdPipeline_Bad_UnknownCommand(t *testing.T) { result := s.cmdPipeline(core.NewOptions(core.Option{Key: "_arg", Value: "unknown"})) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "unknown pipeline command") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "unknown pipeline command") } type pipelineTestIssue struct { @@ -201,9 +199,9 @@ func newPipelineTestServer(t *testing.T, repos map[string]*pipelineTestRepo) *ht return case http.MethodPost: bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) name := payload["name"].(string) repo.Labels[name] = int64(len(repo.Labels) + 1) w.WriteHeader(http.StatusCreated) @@ -236,9 +234,9 @@ func newPipelineTestServer(t *testing.T, repos map[string]*pipelineTestRepo) *ht return case len(parts) == 6 && r.Method == http.MethodPost: bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) next := 1 for number := range repo.Issues { if number >= next { @@ -282,9 +280,9 @@ func newPipelineTestServer(t *testing.T, repos map[string]*pipelineTestRepo) *ht return } bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) if state, ok := payload["state"].(string); ok { issue.State = state } @@ -297,9 +295,9 @@ func newPipelineTestServer(t *testing.T, repos map[string]*pipelineTestRepo) *ht case len(parts) == 8 && parts[7] == "comments" && r.Method == http.MethodPost: number := parseIntString(parts[6]) bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) repo.Comments[number] = append(repo.Comments[number], payload["body"].(string)) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(core.JSONMarshalString(map[string]any{"id": len(repo.Comments[number])}))) @@ -366,7 +364,7 @@ func newPipelineTestServer(t *testing.T, repos map[string]*pipelineTestRepo) *ht return } - if len(parts) >= 8 && parts[5] == "git" && parts[6] == "refs" { + if len(parts) >= 7 && parts[5] == "git" && parts[6] == "refs" { switch { case len(parts) >= 9 && parts[7] == "heads" && r.Method == http.MethodGet: branch := core.Join("/", parts[8:]...) @@ -379,9 +377,9 @@ func newPipelineTestServer(t *testing.T, repos map[string]*pipelineTestRepo) *ht return case len(parts) == 7 && r.Method == http.MethodPost: bodyResult := core.ReadAll(r.Body) - require.True(t, bodyResult.OK) + core.RequireTrue(t, bodyResult.OK) var payload map[string]any - require.True(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) + core.RequireTrue(t, core.JSONUnmarshalString(bodyResult.Value.(string), &payload).OK) ref := payload["ref"].(string) sha := payload["sha"].(string) branch := ref[len("refs/heads/"):] diff --git a/pkg/agentic/pipeline_epic.go b/pkg/agentic/pipeline_epic.go index 4e236c9f..01b4cb4e 100644 --- a/pkg/agentic/pipeline_epic.go +++ b/pkg/agentic/pipeline_epic.go @@ -6,7 +6,7 @@ import ( "context" "sort" - core "dappco.re/go/core" + core "dappco.re/go" ) type PipelineEpicCreateInput struct { diff --git a/pkg/agentic/pipeline_epic_test.go b/pkg/agentic/pipeline_epic_test.go index d2654657..71c92153 100644 --- a/pkg/agentic/pipeline_epic_test.go +++ b/pkg/agentic/pipeline_epic_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineEpic_Good_CreateGroupsIssuesIntoEpic(t *testing.T) { @@ -21,13 +19,13 @@ func TestPipelineEpic_Good_CreateGroupsIssuesIntoEpic(t *testing.T) { s, _ := testPrepWithCore(t, srv) output, err := s.pipelineEpicCreate(context.Background(), PipelineEpicCreateInput{Org: "core", Repo: "go-io"}) - require.NoError(t, err) - require.Len(t, output.Epics, 1) - assert.Equal(t, 13, output.Epics[0].Number) - assert.Equal(t, "epic/13-security", output.Epics[0].Branch) - assert.Contains(t, repo.Issues[13].Body, "- [ ] #10 security(go-io): Validate tokens") - assert.Contains(t, repo.Comments[10][0], "Parent: #13") - assert.Equal(t, "deadbeef", repo.Branches["epic/13-security"]) + core.RequireNoError(t, err) + core.AssertLen(t, output.Epics, 1) + core.AssertEqual(t, 13, output.Epics[0].Number) + core.AssertEqual(t, "epic/13-security", output.Epics[0].Branch) + core.AssertContains(t, repo.Issues[13].Body, "- [ ] #10 security(go-io): Validate tokens") + core.AssertContains(t, repo.Comments[10][0], "Parent: #13") + core.AssertEqual(t, "deadbeef", repo.Branches["epic/13-security"]) } func TestPipelineEpic_Bad_RunRequiresRepoAndNumber(t *testing.T) { @@ -35,10 +33,10 @@ func TestPipelineEpic_Bad_RunRequiresRepoAndNumber(t *testing.T) { result := s.cmdPipelineEpicRun(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "repo and epic number are required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "repo and epic number are required") } func TestPipelineEpic_Ugly_SyncMarksClosedChildrenChecked(t *testing.T) { @@ -57,11 +55,11 @@ func TestPipelineEpic_Ugly_SyncMarksClosedChildrenChecked(t *testing.T) { s, _ := testPrepWithCore(t, srv) output, err := s.pipelineEpicSync(context.Background(), "core", "go-io", 20, false) - require.NoError(t, err) - assert.True(t, output.Updated) - assert.Equal(t, 1, output.Checked) - assert.Contains(t, repo.Issues[20].Body, "- [x] #10 security(go-io): Validate tokens") - assert.Contains(t, repo.Issues[20].Body, "- [ ] #11 security(go-io): Sanitize input") + core.RequireNoError(t, err) + core.AssertTrue(t, output.Updated) + core.AssertEqual(t, 1, output.Checked) + core.AssertContains(t, repo.Issues[20].Body, "- [x] #10 security(go-io): Validate tokens") + core.AssertContains(t, repo.Issues[20].Body, "- [ ] #11 security(go-io): Sanitize input") } func TestPipelineEpic_Run_Good_DryRunDispatchesUncheckedChildren(t *testing.T) { @@ -86,7 +84,7 @@ func TestPipelineEpic_Run_Good_DryRunDispatchesUncheckedChildren(t *testing.T) { DryRun: true, }) - require.NoError(t, err) - assert.Len(t, output.Dispatched, 3) - assert.Equal(t, "epic/20-security", output.Branch) + core.RequireNoError(t, err) + core.AssertLen(t, output.Dispatched, 3) + core.AssertEqual(t, "epic/20-security", output.Branch) } diff --git a/pkg/agentic/pipeline_fix.go b/pkg/agentic/pipeline_fix.go index 6a840dbb..9598d700 100644 --- a/pkg/agentic/pipeline_fix.go +++ b/pkg/agentic/pipeline_fix.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" ) type PipelineFixInput struct { diff --git a/pkg/agentic/pipeline_fix_test.go b/pkg/agentic/pipeline_fix_test.go index ce1d4c4a..be731695 100644 --- a/pkg/agentic/pipeline_fix_test.go +++ b/pkg/agentic/pipeline_fix_test.go @@ -7,9 +7,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineFix_Good_ReviewsPostsComment(t *testing.T) { @@ -19,9 +17,9 @@ func TestPipelineFix_Good_ReviewsPostsComment(t *testing.T) { s, _ := testPrepWithCore(t, srv) output, err := s.pipelineFixReviews(context.Background(), PipelineFixInput{Org: "core", Repo: "go-io", Number: 7}) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Contains(t, repo.Comments[7][0], "Can you fix the code reviews?") + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertContains(t, repo.Comments[7][0], "Can you fix the code reviews?") } func TestPipelineFix_Bad_FormatRequiresWorkspace(t *testing.T) { @@ -33,8 +31,8 @@ func TestPipelineFix_Bad_FormatRequiresWorkspace(t *testing.T) { _, err := s.pipelineFixFormat(context.Background(), PipelineFixInput{Org: "core", Repo: "go-io", Number: 9}) - require.Error(t, err) - assert.Contains(t, err.Error(), "workspace or repo_dir is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "workspace or repo_dir is required") } func TestPipelineFix_Ugly_ThreadsNoopWhenAlreadyResolved(t *testing.T) { @@ -55,15 +53,15 @@ func TestPipelineFix_Ugly_ThreadsNoopWhenAlreadyResolved(t *testing.T) { s, _ := testPrepWithCore(t, srv) output, err := s.pipelineFixThreads(context.Background(), PipelineFixInput{Org: "core", Repo: "go-io", Number: 5}) - require.NoError(t, err) - assert.Equal(t, "noop", output.Action) - assert.Equal(t, "no unresolved review threads remain", output.Message) + core.RequireNoError(t, err) + core.AssertEqual(t, "noop", output.Action) + core.AssertEqual(t, "no unresolved review threads remain", output.Message) } func TestPipelineFix_Format_Good_DryRunCountsFiles(t *testing.T) { dir := t.TempDir() - require.True(t, fs.Write(core.JoinPath(dir, "one.go"), "package main\nfunc main( ){}\n").OK) - require.True(t, fs.Write(core.JoinPath(dir, "two.go"), "package main\nfunc helper( ){}\n").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "one.go"), "package main\nfunc main( ){}\n").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(dir, "two.go"), "package main\nfunc helper( ){}\n").OK) s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(testCore, AgentOptions{}), @@ -79,7 +77,7 @@ func TestPipelineFix_Format_Good_DryRunCountsFiles(t *testing.T) { DryRun: true, }) - require.NoError(t, err) - assert.Equal(t, "format", output.Action) - assert.Equal(t, 2, output.Files) + core.RequireNoError(t, err) + core.AssertEqual(t, "format", output.Action) + core.AssertEqual(t, 2, output.Files) } diff --git a/pkg/agentic/pipeline_monitor.go b/pkg/agentic/pipeline_monitor.go index 92c365db..7ca64322 100644 --- a/pkg/agentic/pipeline_monitor.go +++ b/pkg/agentic/pipeline_monitor.go @@ -6,7 +6,7 @@ import ( "context" "regexp" - core "dappco.re/go/core" + core "dappco.re/go" ) var pipelineEpicBranchPattern = regexp.MustCompile("Epic branch:\\s*`([^`]+)`") diff --git a/pkg/agentic/pipeline_monitor_test.go b/pkg/agentic/pipeline_monitor_test.go index a8e855b3..99914d0e 100644 --- a/pkg/agentic/pipeline_monitor_test.go +++ b/pkg/agentic/pipeline_monitor_test.go @@ -6,8 +6,7 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineMonitor_Good_AutoIntervenesAndMerges(t *testing.T) { @@ -60,11 +59,11 @@ func TestPipelineMonitor_Good_AutoIntervenesAndMerges(t *testing.T) { Repo: "go-io", }, &pipelineForgeMetaReader{subsystem: s, org: "core"}) - require.NoError(t, err) - assert.Len(t, output.Actions, 3) - assert.Contains(t, repo.Comments[1][0], "Can you fix the merge conflict?") - assert.Contains(t, repo.Comments[2][0], "Can you fix the code reviews?") - assert.Contains(t, repo.Merged, 3) + core.RequireNoError(t, err) + core.AssertLen(t, output.Actions, 3) + core.AssertContains(t, repo.Comments[1][0], "Can you fix the merge conflict?") + core.AssertContains(t, repo.Comments[2][0], "Can you fix the code reviews?") + core.AssertContains(t, repo.Merged, 3) } func TestPipelineMonitor_Bad_NoToken(t *testing.T) { @@ -73,8 +72,8 @@ func TestPipelineMonitor_Bad_NoToken(t *testing.T) { _, err := s.pipelineMonitorWithReader(context.Background(), PipelineMonitorInput{Org: "core", Repo: "go-io"}, &pipelineForgeMetaReader{subsystem: s, org: "core"}) - require.Error(t, err) - assert.Contains(t, err.Error(), "no Forge token configured") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "no Forge token configured") } func TestPipelineMonitor_Ugly_NoActionWhenChecksPending(t *testing.T) { @@ -99,6 +98,6 @@ func TestPipelineMonitor_Ugly_NoActionWhenChecksPending(t *testing.T) { Repo: "go-io", }, &pipelineForgeMetaReader{subsystem: s, org: "core"}) - require.NoError(t, err) - assert.Empty(t, output.Actions) + core.RequireNoError(t, err) + core.AssertEmpty(t, output.Actions) } diff --git a/pkg/agentic/pipeline_onboard.go b/pkg/agentic/pipeline_onboard.go index f7587991..616e541a 100644 --- a/pkg/agentic/pipeline_onboard.go +++ b/pkg/agentic/pipeline_onboard.go @@ -5,7 +5,7 @@ package agentic import ( "context" - core "dappco.re/go/core" + core "dappco.re/go" ) type PipelineOnboardInput struct { diff --git a/pkg/agentic/pipeline_onboard_test.go b/pkg/agentic/pipeline_onboard_test.go index 2b96017f..3b67537f 100644 --- a/pkg/agentic/pipeline_onboard_test.go +++ b/pkg/agentic/pipeline_onboard_test.go @@ -6,9 +6,7 @@ import ( "context" "testing" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineOnboard_Good_ChainsAuditEpicAndDispatch(t *testing.T) { @@ -29,12 +27,12 @@ func TestPipelineOnboard_Good_ChainsAuditEpicAndDispatch(t *testing.T) { DispatchDryRun: true, }) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Len(t, output.Audit.Created, 3) - require.Len(t, output.Runs, 1) - assert.Len(t, output.Runs[0].Dispatched, 3) - assert.Empty(t, output.Direct) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertLen(t, output.Audit.Created, 3) + core.AssertLen(t, output.Runs, 1) + core.AssertLen(t, output.Runs[0].Dispatched, 3) + core.AssertEmpty(t, output.Direct) } func TestPipelineOnboard_Bad_MissingRepo(t *testing.T) { @@ -42,10 +40,10 @@ func TestPipelineOnboard_Bad_MissingRepo(t *testing.T) { result := s.cmdPipelineOnboard(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "repo is required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "repo is required") } func TestPipelineOnboard_Ugly_DirectDispatchWhenEpicNotCreated(t *testing.T) { @@ -66,7 +64,7 @@ func TestPipelineOnboard_Ugly_DirectDispatchWhenEpicNotCreated(t *testing.T) { DispatchDryRun: true, }) - require.NoError(t, err) - assert.Empty(t, output.Runs) - assert.Len(t, output.Direct, 2) + core.RequireNoError(t, err) + core.AssertEmpty(t, output.Runs) + core.AssertLen(t, output.Direct, 2) } diff --git a/pkg/agentic/pipeline_training.go b/pkg/agentic/pipeline_training.go index 151af41a..0c0f34dc 100644 --- a/pkg/agentic/pipeline_training.go +++ b/pkg/agentic/pipeline_training.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" ) // input := PipelineTrainingCaptureInput{Org: "core", Repo: "go-io", Number: 42} @@ -190,8 +190,12 @@ func (s *PrepSubsystem) pipelineTrainingReadGitDiff(ctx context.Context, org, re } if meta.BaseBranch != "" && meta.HeadBranch != "" { - _ = process.RunIn(ctx, repoDir, "git", "fetch", "origin", meta.BaseBranch) - _ = process.RunIn(ctx, repoDir, "git", "fetch", "origin", meta.HeadBranch) + if fetchBaseResult := process.RunIn(ctx, repoDir, "git", "fetch", "origin", meta.BaseBranch); !fetchBaseResult.OK { + core.Warn("pipelineTrainingReadGitDiff: failed to fetch base branch", "repo", repo, "branch", meta.BaseBranch, "reason", fetchBaseResult.Value) + } + if fetchHeadResult := process.RunIn(ctx, repoDir, "git", "fetch", "origin", meta.HeadBranch); !fetchHeadResult.OK { + core.Warn("pipelineTrainingReadGitDiff: failed to fetch head branch", "repo", repo, "branch", meta.HeadBranch, "reason", fetchHeadResult.Value) + } diffResult := process.RunIn(ctx, repoDir, "git", "diff", core.Concat("origin/", meta.BaseBranch), core.Concat("origin/", meta.HeadBranch)) if diffResult.OK && core.Trim(resultText(diffResult)) != "" { return resultText(diffResult), "git.diff", nil diff --git a/pkg/agentic/pipeline_training_test.go b/pkg/agentic/pipeline_training_test.go index e05d5ab7..81df0337 100644 --- a/pkg/agentic/pipeline_training_test.go +++ b/pkg/agentic/pipeline_training_test.go @@ -9,9 +9,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPipelineTraining_Good_RootHelp(t *testing.T) { @@ -19,12 +17,12 @@ func TestPipelineTraining_Good_RootHelp(t *testing.T) { output := captureStdout(t, func() { result := s.cmdPipelineTraining(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) - assert.Contains(t, output, "core-agent pipeline/training/capture") - assert.Contains(t, output, "core-agent pipeline/training/stats") - assert.Contains(t, output, "core-agent pipeline/training/export") + core.AssertContains(t, output, "core-agent pipeline/training/capture") + core.AssertContains(t, output, "core-agent pipeline/training/stats") + core.AssertContains(t, output, "core-agent pipeline/training/export") } func TestPipelineTraining_Good_CaptureWritesJournal(t *testing.T) { @@ -48,26 +46,26 @@ func TestPipelineTraining_Good_CaptureWritesJournal(t *testing.T) { core.Option{Key: "_arg", Value: "7"}, core.Option{Key: "repo", Value: "go-io"}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) }) entries := readPipelineTrainingJournal(pipelineTrainingJournalPath()) - require.Len(t, entries, 1) - assert.Contains(t, output, "go-io#7") - assert.Equal(t, 2, entries[0].CodeRabbitFindings) - assert.Equal(t, "forge.pull.diff", entries[0].DiffSource) - assert.Contains(t, entries[0].Diff, "diff --git") + core.AssertLen(t, entries, 1) + core.AssertContains(t, output, "go-io#7") + core.AssertEqual(t, 2, entries[0].CodeRabbitFindings) + core.AssertEqual(t, "forge.pull.diff", entries[0].DiffSource) + core.AssertContains(t, entries[0].Diff, "diff --git") } func TestPipelineTraining_Good_StatsAggregatesJournal(t *testing.T) { s, _ := testPrepWithCore(t, nil) - require.NoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ + core.RequireNoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ CapturedAt: "2026-04-25T12:00:00Z", Repo: "go-io", PRNumber: 7, CodeRabbitFindings: 0, })) - require.NoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ + core.RequireNoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ CapturedAt: "2026-04-25T12:10:00Z", Repo: "go-log", PRNumber: 8, @@ -77,30 +75,30 @@ func TestPipelineTraining_Good_StatsAggregatesJournal(t *testing.T) { var stats PipelineTrainingStats output := captureStdout(t, func() { result := s.cmdPipelineTrainingStats(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) var ok bool stats, ok = result.Value.(PipelineTrainingStats) - require.True(t, ok) + core.RequireTrue(t, ok) }) - assert.Equal(t, 2, stats.TotalPRs) - assert.Equal(t, 1, stats.ZeroFindingPRs) - assert.Equal(t, 1, stats.ByRepo["go-io"]) - assert.Equal(t, 1, stats.ByRepo["go-log"]) - assert.Contains(t, output, "go-io") - assert.Contains(t, output, "go-log") + core.AssertEqual(t, 2, stats.TotalPRs) + core.AssertEqual(t, 1, stats.ZeroFindingPRs) + core.AssertEqual(t, 1, stats.ByRepo["go-io"]) + core.AssertEqual(t, 1, stats.ByRepo["go-log"]) + core.AssertContains(t, output, "go-io") + core.AssertContains(t, output, "go-log") } func TestPipelineTraining_Good_ExportWritesZeroFindingOnly(t *testing.T) { s, _ := testPrepWithCore(t, nil) - require.NoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ + core.RequireNoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ CapturedAt: "2026-04-25T12:00:00Z", Repo: "go-io", PRNumber: 7, CodeRabbitFindings: 0, Diff: "clean diff", })) - require.NoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ + core.RequireNoError(t, appendJSONLRecord(pipelineTrainingJournalPath(), PipelineTrainingEntry{ CapturedAt: "2026-04-25T12:10:00Z", Repo: "go-log", PRNumber: 8, @@ -109,12 +107,12 @@ func TestPipelineTraining_Good_ExportWritesZeroFindingOnly(t *testing.T) { })) result := s.cmdPipelineTrainingExport(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) exported := readPipelineTrainingJournal(pipelineTrainingExportPath()) - require.Len(t, exported, 1) - assert.Equal(t, "go-io", exported[0].Repo) - assert.Equal(t, 0, exported[0].CodeRabbitFindings) + core.AssertLen(t, exported, 1) + core.AssertEqual(t, "go-io", exported[0].Repo) + core.AssertEqual(t, 0, exported[0].CodeRabbitFindings) } func TestPipelineTraining_Bad_CaptureRequiresRepoAndNumber(t *testing.T) { @@ -122,10 +120,10 @@ func TestPipelineTraining_Bad_CaptureRequiresRepoAndNumber(t *testing.T) { result := s.cmdPipelineTrainingCapture(core.NewOptions()) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "repo and pull request number are required") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "repo and pull request number are required") } func TestPipelineTraining_Bad_CaptureRejectsOpenPullRequest(t *testing.T) { @@ -147,17 +145,17 @@ func TestPipelineTraining_Bad_CaptureRejectsOpenPullRequest(t *testing.T) { core.Option{Key: "repo", Value: "go-io"}, )) - require.False(t, result.OK) + core.AssertFalse(t, result.OK) err, ok := result.Value.(error) - require.True(t, ok) - assert.Contains(t, err.Error(), "pull request is not merged") + core.RequireTrue(t, ok) + core.AssertContains(t, err.Error(), "pull request is not merged") } func TestPipelineTraining_Ugly_CaptureFallsBackToGitShow(t *testing.T) { root := t.TempDir() setTestWorkspace(t, root) - c := core.New() + c := testCore s := &PrepSubsystem{ ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), codePath: t.TempDir(), @@ -167,7 +165,7 @@ func TestPipelineTraining_Ugly_CaptureFallsBackToGitShow(t *testing.T) { } repoDir, headSHA := createTrainingGitRepo(t, c, s.codePath) - assert.NotEmpty(t, repoDir) + core.AssertNotEmpty(t, repoDir) srv := newTrainingTestServer(t, trainingTestServerConfig{ Repo: "go-io", @@ -189,24 +187,24 @@ func TestPipelineTraining_Ugly_CaptureFallsBackToGitShow(t *testing.T) { Repo: "go-io", Number: 9, }) - require.NoError(t, err) - assert.Equal(t, "git.show", output.DiffSource) + core.RequireNoError(t, err) + core.AssertEqual(t, "git.show", output.DiffSource) } func TestPipelineTraining_Ugly_StatsSkipsCorruptJournalRows(t *testing.T) { s, _ := testPrepWithCore(t, nil) - require.NoError(t, ensureParentDir(pipelineTrainingJournalPath())) - require.True(t, fs.WriteAtomic(pipelineTrainingJournalPath(), core.Concat( + core.RequireNoError(t, ensureParentDir(pipelineTrainingJournalPath())) + core.RequireTrue(t, fs.WriteAtomic(pipelineTrainingJournalPath(), core.Concat( "{not-json}\n", core.JSONMarshalString(PipelineTrainingEntry{CapturedAt: "2026-04-25T12:00:00Z", Repo: "go-io", PRNumber: 7, CodeRabbitFindings: 0}), "\n", )).OK) result := s.cmdPipelineTrainingStats(core.NewOptions()) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) stats, ok := result.Value.(PipelineTrainingStats) - require.True(t, ok) - assert.Equal(t, 1, stats.TotalPRs) + core.RequireTrue(t, ok) + core.AssertEqual(t, 1, stats.TotalPRs) } type trainingTestServerConfig struct { @@ -278,15 +276,15 @@ func newTrainingTestServer(t *testing.T, config trainingTestServerConfig) *httpt func createTrainingGitRepo(t *testing.T, c *core.Core, codePath string) (string, string) { t.Helper() repoDir := core.JoinPath(codePath, "core", "go-io") - require.True(t, fs.EnsureDir(repoDir).OK) - require.True(t, c.Process().Run(context.Background(), "git", "init", "-b", "dev", repoDir).OK) - require.True(t, c.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) - require.True(t, c.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@example.com").OK) - require.True(t, fs.Write(core.JoinPath(repoDir, "main.go"), "package main\n\nfunc main() {}\n").OK) - require.True(t, c.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) - require.True(t, c.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "init").OK) + core.RequireTrue(t, fs.EnsureDir(repoDir).OK) + core.RequireTrue(t, c.Process().Run(context.Background(), "git", "init", "-b", "dev", repoDir).OK) + core.RequireTrue(t, c.Process().RunIn(context.Background(), repoDir, "git", "config", "user.name", "Test").OK) + core.RequireTrue(t, c.Process().RunIn(context.Background(), repoDir, "git", "config", "user.email", "test@example.com").OK) + core.RequireTrue(t, fs.Write(core.JoinPath(repoDir, "main.go"), "package main\n\nfunc main() {}\n").OK) + core.RequireTrue(t, c.Process().RunIn(context.Background(), repoDir, "git", "add", ".").OK) + core.RequireTrue(t, c.Process().RunIn(context.Background(), repoDir, "git", "commit", "-m", "init").OK) head := c.Process().RunIn(context.Background(), repoDir, "git", "rev-parse", "HEAD") - require.True(t, head.OK) + core.RequireTrue(t, head.OK) return repoDir, core.Trim(resultText(head)) } diff --git a/pkg/agentic/plan.go b/pkg/agentic/plan.go index 7aae8b06..913b631d 100644 --- a/pkg/agentic/plan.go +++ b/pkg/agentic/plan.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" coremcp "dappco.re/go/mcp/pkg/mcp" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/plan_compat.go b/pkg/agentic/plan_compat.go index da1d030f..49c54187 100644 --- a/pkg/agentic/plan_compat.go +++ b/pkg/agentic/plan_compat.go @@ -6,7 +6,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/agentic/plan_compat_test.go b/pkg/agentic/plan_compat_test.go index 2de07124..576848c1 100644 --- a/pkg/agentic/plan_compat_test.go +++ b/pkg/agentic/plan_compat_test.go @@ -6,8 +6,7 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) func TestPlancompat_PlanCreateCompat_Good(t *testing.T) { @@ -22,11 +21,11 @@ func TestPlancompat_PlanCreateCompat_Good(t *testing.T) { {Name: "Setup", Tasks: []PlanTask{{Title: "Review RFC"}}}, }, }) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Contains(t, output.Plan.Slug, "compatibility-plan") - assert.Equal(t, "draft", output.Plan.Status) - assert.Equal(t, 1, output.Plan.Phases) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertContains(t, output.Plan.Slug, "compatibility-plan") + core.AssertEqual(t, "draft", output.Plan.Status) + core.AssertEqual(t, 1, output.Plan.Phases) } func TestPlancompat_PlanGetCompat_Good_BySlug(t *testing.T) { @@ -41,18 +40,18 @@ func TestPlancompat_PlanGetCompat_Good_BySlug(t *testing.T) { {Name: "Setup", Tasks: []PlanTask{{Title: "Review RFC", Status: "completed"}, {Title: "Patch code"}}}, }, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) _, output, err := s.planGetCompat(context.Background(), nil, PlanReadInput{Slug: plan.Slug}) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Equal(t, plan.Slug, output.Plan.Slug) - assert.Equal(t, 2, output.Plan.Progress.Total) - assert.Equal(t, 1, output.Plan.Progress.Completed) - assert.Equal(t, 50, output.Plan.Progress.Percentage) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, plan.Slug, output.Plan.Slug) + core.AssertEqual(t, 2, output.Plan.Progress.Total) + core.AssertEqual(t, 1, output.Plan.Progress.Completed) + core.AssertEqual(t, 50, output.Plan.Progress.Percentage) } func TestPlancompat_PlanUpdateStatusCompat_Good(t *testing.T) { @@ -64,18 +63,18 @@ func TestPlancompat_PlanUpdateStatusCompat_Good(t *testing.T) { Title: "Status Compat", Description: "Update by slug", }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) _, output, err := s.planUpdateStatusCompat(context.Background(), nil, PlanStatusUpdateInput{ Slug: plan.Slug, Status: "active", }) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Equal(t, "active", output.Plan.Status) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "active", output.Plan.Status) } func TestPlancompat_PlanArchiveCompat_Good(t *testing.T) { @@ -87,22 +86,22 @@ func TestPlancompat_PlanArchiveCompat_Good(t *testing.T) { Title: "Archive Compat", Description: "Archive by slug", }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, err := readPlan(PlansRoot(), created.ID) - require.NoError(t, err) + core.RequireNoError(t, err) _, output, err := s.planArchiveCompat(context.Background(), nil, PlanDeleteInput{ Slug: plan.Slug, Reason: "No longer needed", }) - require.NoError(t, err) - assert.True(t, output.Success) - assert.Equal(t, plan.Slug, output.Archived) + core.RequireNoError(t, err) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, plan.Slug, output.Archived) archivedPlan, err := readPlan(PlansRoot(), plan.Slug) - require.NoError(t, err) - assert.Equal(t, "archived", archivedPlan.Status) - assert.False(t, archivedPlan.ArchivedAt.IsZero()) - assert.Contains(t, archivedPlan.Notes, "No longer needed") + core.RequireNoError(t, err) + core.AssertEqual(t, "archived", archivedPlan.Status) + core.AssertFalse(t, archivedPlan.ArchivedAt.IsZero()) + core.AssertContains(t, archivedPlan.Notes, "No longer needed") } diff --git a/pkg/agentic/plan_crud_test.go b/pkg/agentic/plan_crud_test.go index 9a86d918..aadcc5e7 100644 --- a/pkg/agentic/plan_crud_test.go +++ b/pkg/agentic/plan_crud_test.go @@ -8,9 +8,7 @@ import ( "testing" "time" - core "dappco.re/go/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + core "dappco.re/go" ) // newTestPrep creates a PrepSubsystem for testing with testCore wired in. @@ -40,13 +38,13 @@ func TestPlan_PlanCreate_Good(t *testing.T) { }, Notes: "Priority: high", }) - require.NoError(t, err) - assert.True(t, out.Success) - assert.NotEmpty(t, out.ID) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + core.AssertNotEmpty(t, out.ID) assertCoreIDFormat(t, out.ID) - assert.NotEmpty(t, out.Path) + core.AssertNotEmpty(t, out.Path) - assert.True(t, fs.Exists(out.Path)) + core.AssertTrue(t, fs.Exists(out.Path)) } func TestPlan_PlanCreate_Good_UniqueIDs(t *testing.T) { @@ -58,16 +56,16 @@ func TestPlan_PlanCreate_Good_UniqueIDs(t *testing.T) { Title: "Repeated Title", Objective: "Repeated objective", }) - require.NoError(t, err) + core.RequireNoError(t, err) assertCoreIDFormat(t, first.ID) _, second, err := s.planCreate(context.Background(), nil, PlanCreateInput{ Title: "Repeated Title", Objective: "Repeated objective", }) - require.NoError(t, err) + core.RequireNoError(t, err) assertCoreIDFormat(t, second.ID) - assert.NotEqual(t, first.ID, second.ID) + core.AssertNotEqual(t, first.ID, second.ID) } func TestPlan_PlanCreate_Bad_MissingTitle(t *testing.T) { @@ -75,8 +73,8 @@ func TestPlan_PlanCreate_Bad_MissingTitle(t *testing.T) { _, _, err := s.planCreate(context.Background(), nil, PlanCreateInput{ Objective: "something", }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "title is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "title is required") } func TestPlan_PlanCreate_Bad_MissingObjective(t *testing.T) { @@ -84,8 +82,8 @@ func TestPlan_PlanCreate_Bad_MissingObjective(t *testing.T) { _, _, err := s.planCreate(context.Background(), nil, PlanCreateInput{ Title: "My Plan", }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "objective is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "objective is required") } func TestPlan_PlanCreate_Good_DefaultPhaseStatus(t *testing.T) { @@ -98,14 +96,14 @@ func TestPlan_PlanCreate_Good_DefaultPhaseStatus(t *testing.T) { Objective: "Test defaults", Phases: []Phase{{Name: "Phase 1"}, {Name: "Phase 2"}}, }) - require.NoError(t, err) + core.RequireNoError(t, err) plan, readErr := readPlan(PlansRoot(), out.ID) - require.NoError(t, readErr) - assert.Equal(t, "pending", plan.Phases[0].Status) - assert.Equal(t, "pending", plan.Phases[1].Status) - assert.Equal(t, 1, plan.Phases[0].Number) - assert.Equal(t, 2, plan.Phases[1].Number) + core.RequireNoError(t, readErr) + core.AssertEqual(t, "pending", plan.Phases[0].Status) + core.AssertEqual(t, "pending", plan.Phases[1].Status) + core.AssertEqual(t, 1, plan.Phases[0].Number) + core.AssertEqual(t, 2, plan.Phases[1].Number) } // --- planRead (MCP handler) --- @@ -119,21 +117,21 @@ func TestPlan_PlanRead_Good(t *testing.T) { Title: "Read Test", Objective: "Verify read works", }) - require.NoError(t, err) + core.RequireNoError(t, err) _, readOut, err := s.planRead(context.Background(), nil, PlanReadInput{ID: createOut.ID}) - require.NoError(t, err) - assert.True(t, readOut.Success) - assert.Equal(t, createOut.ID, readOut.Plan.ID) - assert.Equal(t, "Read Test", readOut.Plan.Title) - assert.Equal(t, "draft", readOut.Plan.Status) + core.RequireNoError(t, err) + core.AssertTrue(t, readOut.Success) + core.AssertEqual(t, createOut.ID, readOut.Plan.ID) + core.AssertEqual(t, "Read Test", readOut.Plan.Title) + core.AssertEqual(t, "draft", readOut.Plan.Status) } func TestPlan_PlanRead_Bad_MissingID(t *testing.T) { s := newTestPrep(t) _, _, err := s.planRead(context.Background(), nil, PlanReadInput{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "id is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "id is required") } func TestPlan_PlanRead_Bad_NotFound(t *testing.T) { @@ -142,8 +140,8 @@ func TestPlan_PlanRead_Bad_NotFound(t *testing.T) { s := newTestPrep(t) _, _, err := s.planRead(context.Background(), nil, PlanReadInput{ID: "nonexistent"}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "not found") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "not found") } // --- planUpdate (MCP handler) --- @@ -162,9 +160,9 @@ func TestPlan_PlanUpdate_Good_Status(t *testing.T) { ID: createOut.ID, Status: "ready", }) - require.NoError(t, err) - assert.True(t, updateOut.Success) - assert.Equal(t, "ready", updateOut.Plan.Status) + core.RequireNoError(t, err) + core.AssertTrue(t, updateOut.Success) + core.AssertEqual(t, "ready", updateOut.Plan.Status) } func TestPlan_PlanUpdate_Good_PartialUpdate(t *testing.T) { @@ -183,11 +181,11 @@ func TestPlan_PlanUpdate_Good_PartialUpdate(t *testing.T) { Title: "New Title", Agent: "codex", }) - require.NoError(t, err) - assert.Equal(t, "New Title", updateOut.Plan.Title) - assert.Equal(t, "Original objective", updateOut.Plan.Objective) - assert.Equal(t, "Original notes", updateOut.Plan.Notes) - assert.Equal(t, "codex", updateOut.Plan.Agent) + core.RequireNoError(t, err) + core.AssertEqual(t, "New Title", updateOut.Plan.Title) + core.AssertEqual(t, "Original objective", updateOut.Plan.Objective) + core.AssertEqual(t, "Original notes", updateOut.Plan.Notes) + core.AssertEqual(t, "codex", updateOut.Plan.Agent) } func TestPlan_PlanUpdate_Good_AllStatusTransitions(t *testing.T) { @@ -204,8 +202,8 @@ func TestPlan_PlanUpdate_Good_AllStatusTransitions(t *testing.T) { _, out, err := s.planUpdate(context.Background(), nil, PlanUpdateInput{ ID: createOut.ID, Status: status, }) - require.NoError(t, err, "transition to %s", status) - assert.Equal(t, status, out.Plan.Status) + core.RequireNoError(t, err, "transition to %s", status) + core.AssertEqual(t, status, out.Plan.Status) } } @@ -221,15 +219,15 @@ func TestPlan_PlanUpdate_Bad_InvalidStatus(t *testing.T) { _, _, err := s.planUpdate(context.Background(), nil, PlanUpdateInput{ ID: createOut.ID, Status: "invalid_status", }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid status") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "invalid status") } func TestPlan_PlanUpdate_Bad_MissingID(t *testing.T) { s := newTestPrep(t) _, _, err := s.planUpdate(context.Background(), nil, PlanUpdateInput{Status: "ready"}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "id is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "id is required") } func TestPlan_PlanUpdate_Good_ReplacePhases(t *testing.T) { @@ -247,9 +245,9 @@ func TestPlan_PlanUpdate_Good_ReplacePhases(t *testing.T) { ID: createOut.ID, Phases: []Phase{{Number: 1, Name: "New Phase", Status: "done"}, {Number: 2, Name: "Phase 2"}}, }) - require.NoError(t, err) - assert.Len(t, updateOut.Plan.Phases, 2) - assert.Equal(t, "New Phase", updateOut.Plan.Phases[0].Name) + core.RequireNoError(t, err) + core.AssertLen(t, updateOut.Plan.Phases, 2) + core.AssertEqual(t, "New Phase", updateOut.Plan.Phases[0].Name) } // --- planDelete (MCP handler) --- @@ -264,8 +262,8 @@ func TestPlan_PlanDelete_Good(t *testing.T) { }) planBeforeDelete, err := readPlan(PlansRoot(), createOut.ID) - require.NoError(t, err) - require.NoError(t, writePlanStates(planBeforeDelete.Slug, []WorkspaceState{{ + core.RequireNoError(t, err) + core.RequireNoError(t, writePlanStates(planBeforeDelete.Slug, []WorkspaceState{{ Key: "pattern", Value: "observer", }})) @@ -274,22 +272,22 @@ func TestPlan_PlanDelete_Good(t *testing.T) { ID: createOut.ID, Reason: "No longer needed", }) - require.NoError(t, err) - assert.True(t, delOut.Success) - assert.Equal(t, createOut.ID, delOut.Deleted) + core.RequireNoError(t, err) + core.AssertTrue(t, delOut.Success) + core.AssertEqual(t, createOut.ID, delOut.Deleted) - assert.False(t, fs.Exists(createOut.Path)) - assert.False(t, fs.Exists(statePath(planBeforeDelete.Slug))) + core.AssertFalse(t, fs.Exists(createOut.Path)) + core.AssertFalse(t, fs.Exists(statePath(planBeforeDelete.Slug))) _, readErr := readPlan(PlansRoot(), createOut.ID) - require.Error(t, readErr) + core.AssertError(t, readErr) } func TestPlan_PlanDelete_Bad_MissingID(t *testing.T) { s := newTestPrep(t) _, _, err := s.planDelete(context.Background(), nil, PlanDeleteInput{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "id is required") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "id is required") } func TestPlan_PlanDelete_Bad_NotFound(t *testing.T) { @@ -298,8 +296,8 @@ func TestPlan_PlanDelete_Bad_NotFound(t *testing.T) { s := newTestPrep(t) _, _, err := s.planDelete(context.Background(), nil, PlanDeleteInput{ID: "nonexistent"}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "not found") + core.AssertError(t, err) + core.AssertContains(t, err.Error(), "not found") } // --- planList (MCP handler) --- @@ -310,9 +308,9 @@ func TestPlan_PlanList_Good_Empty(t *testing.T) { s := newTestPrep(t) _, out, err := s.planList(context.Background(), nil, PlanListInput{}) - require.NoError(t, err) - assert.True(t, out.Success) - assert.Equal(t, 0, out.Count) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + core.AssertEqual(t, 0, out.Count) } func TestPlan_PlanList_Good_Multiple(t *testing.T) { @@ -325,8 +323,8 @@ func TestPlan_PlanList_Good_Multiple(t *testing.T) { s.planCreate(context.Background(), nil, PlanCreateInput{Title: "C", Objective: "C", Repo: "go-io"}) _, out, err := s.planList(context.Background(), nil, PlanListInput{}) - require.NoError(t, err) - assert.Equal(t, 3, out.Count) + core.RequireNoError(t, err) + core.AssertEqual(t, 3, out.Count) } func TestPlan_PlanList_Good_FilterByRepo(t *testing.T) { @@ -339,8 +337,8 @@ func TestPlan_PlanList_Good_FilterByRepo(t *testing.T) { s.planCreate(context.Background(), nil, PlanCreateInput{Title: "C", Objective: "C", Repo: "go-io"}) _, out, err := s.planList(context.Background(), nil, PlanListInput{Repo: "go-io"}) - require.NoError(t, err) - assert.Equal(t, 2, out.Count) + core.RequireNoError(t, err) + core.AssertEqual(t, 2, out.Count) } func TestPlan_HandlePlanCreate_Good(t *testing.T) { @@ -361,18 +359,18 @@ func TestPlan_HandlePlanCreate_Good(t *testing.T) { }}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(PlanCreateOutput) - require.True(t, ok) - assert.True(t, output.Success) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) assertCoreIDFormat(t, output.ID) read, err := readPlan(PlansRoot(), output.ID) - require.NoError(t, err) - require.Len(t, read.Phases, 1) - assert.Equal(t, "Register actions", read.Phases[0].Name) - assert.Equal(t, []string{"plan.create exists", "tests cover handlers"}, read.Phases[0].Criteria) - assert.Equal(t, 2, read.Phases[0].Tests) + core.RequireNoError(t, err) + core.AssertLen(t, read.Phases, 1) + core.AssertEqual(t, "Register actions", read.Phases[0].Name) + core.AssertEqual(t, []string{"plan.create exists", "tests cover handlers"}, read.Phases[0].Criteria) + core.AssertEqual(t, 2, read.Phases[0].Tests) } func TestPlan_HandlePlanUpdate_Good_JSONPhases(t *testing.T) { @@ -384,7 +382,7 @@ func TestPlan_HandlePlanUpdate_Good_JSONPhases(t *testing.T) { Title: "Update via action", Objective: "Parse phase JSON from action options", }) - require.NoError(t, err) + core.RequireNoError(t, err) result := s.handlePlanUpdate(context.Background(), core.NewOptions( core.Option{Key: "id", Value: created.ID}, @@ -393,15 +391,15 @@ func TestPlan_HandlePlanUpdate_Good_JSONPhases(t *testing.T) { core.Option{Key: "phases", Value: `[{"number":1,"name":"Review drift","status":"pending","criteria":["actions registered"]}]`}, )) - require.True(t, result.OK) + core.RequireTrue(t, result.OK) output, ok := result.Value.(PlanUpdateOutput) - require.True(t, ok) - assert.True(t, output.Success) - assert.Equal(t, "ready", output.Plan.Status) - assert.Equal(t, "codex", output.Plan.Agent) - require.Len(t, output.Plan.Phases, 1) - assert.Equal(t, "Review drift", output.Plan.Phases[0].Name) - assert.Equal(t, []string{"actions registered"}, output.Plan.Phases[0].Criteria) + core.RequireTrue(t, ok) + core.AssertTrue(t, output.Success) + core.AssertEqual(t, "ready", output.Plan.Status) + core.AssertEqual(t, "codex", output.Plan.Agent) + core.AssertLen(t, output.Plan.Phases, 1) + core.AssertEqual(t, "Review drift", output.Plan.Phases[0].Name) + core.AssertEqual(t, []string{"actions registered"}, output.Plan.Phases[0].Criteria) } func TestPlan_PlanList_Good_FilterByStatus(t *testing.T) { @@ -414,9 +412,9 @@ func TestPlan_PlanList_Good_FilterByStatus(t *testing.T) { s.planUpdate(context.Background(), nil, PlanUpdateInput{ID: c2.ID, Status: "ready"}) _, out, err := s.planList(context.Background(), nil, PlanListInput{Status: "ready"}) - require.NoError(t, err) - assert.Equal(t, 1, out.Count) - assert.Equal(t, "ready", out.Plans[0].Status) + core.RequireNoError(t, err) + core.AssertEqual(t, 1, out.Count) + core.AssertEqual(t, "ready", out.Plans[0].Status) } func TestPlan_PlanList_Good_IgnoresNonJSON(t *testing.T) { @@ -431,8 +429,8 @@ func TestPlan_PlanList_Good_IgnoresNonJSON(t *testing.T) { fs.Write(plansDir+"/notes.txt", "not a plan") _, out, err := s.planList(context.Background(), nil, PlanListInput{}) - require.NoError(t, err) - assert.Equal(t, 1, out.Count, "should skip non-JSON files") + core.RequireNoError(t, err) + core.AssertEqual(t, 1, out.Count, "should skip non-JSON files") } func TestPlan_PlanList_Good_DefaultLimit(t *testing.T) { @@ -445,26 +443,28 @@ func TestPlan_PlanList_Good_DefaultLimit(t *testing.T) { Title: core.Sprintf("Plan %d", i+1), Objective: "Test default list limit", }) - require.NoError(t, err) + core.RequireNoError(t, err) } _, out, err := s.planList(context.Background(), nil, PlanListInput{}) - require.NoError(t, err) - assert.Equal(t, 20, out.Count) - assert.Len(t, out.Plans, 20) + core.RequireNoError(t, err) + core.AssertEqual(t, 20, out.Count) + core.AssertLen(t, out.Plans, 20) } // --- planPath edge cases --- func TestPlan_PlanPath_Bad_PathTraversal(t *testing.T) { p := planPath("/tmp/plans", "../../etc/passwd") - assert.NotContains(t, p, "..") + base := core.PathBase(p) + core.AssertNotContains(t, p, "..") + core.AssertEqual(t, "passwd.json", base) } func TestPlan_PlanPath_Bad_Dot(t *testing.T) { - assert.Contains(t, planPath("/tmp", "."), "invalid") - assert.Contains(t, planPath("/tmp", ".."), "invalid") - assert.Contains(t, planPath("/tmp", ""), "invalid") + core.AssertContains(t, planPath("/tmp", "."), "invalid") + core.AssertContains(t, planPath("/tmp", ".."), "invalid") + core.AssertContains(t, planPath("/tmp", ""), "invalid") } // --- planCreate Ugly --- @@ -479,9 +479,9 @@ func TestPlan_PlanCreate_Ugly_VeryLongTitle(t *testing.T) { Title: longTitle, Objective: "Test very long title handling", }) - require.NoError(t, err) - assert.True(t, out.Success) - assert.NotEmpty(t, out.ID) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + core.AssertNotEmpty(t, out.ID) assertCoreIDFormat(t, out.ID) } @@ -494,12 +494,12 @@ func TestPlan_PlanCreate_Ugly_UnicodeTitle(t *testing.T) { Title: "\u00e9\u00e0\u00fc\u00f1\u00f0 Plan \u2603\u2764\u270c", Objective: "Handle unicode gracefully", }) - require.NoError(t, err) - assert.True(t, out.Success) - assert.NotEmpty(t, out.ID) + core.RequireNoError(t, err) + core.AssertTrue(t, out.Success) + core.AssertNotEmpty(t, out.ID) assertCoreIDFormat(t, out.ID) // Should be readable from disk - assert.True(t, fs.Exists(out.Path)) + core.AssertTrue(t, fs.Exists(out.Path)) } // --- planRead Ugly --- @@ -511,8 +511,8 @@ func TestPlan_PlanRead_Ugly_SpecialCharsInID(t *testing.T) { s := newTestPrep(t) // Try to read with special chars — should safely not find it _, _, err := s.planRead(context.Background(), nil, PlanReadInput{ID: "plan-with-