Running with Native Go
A Miru Go extension’s single source file can be driven in two stages. This page covers Stage 1 · Native Go (development): write the extension as an ordinary Go package to get full IDE support and unit testing.
Key point: Stage 1 and Stage 2 (Running with Scriggo) use the same source file. Logic you get working locally with native Go is compiled and executed unchanged by Scriggo at runtime, so there is no “works locally but fails once installed” gap. How the host drives it at runtime is on Running with Scriggo.
Initialize the project
Section titled “Initialize the project”mkdir my.extension && cd my.extensiongo mod init my.extensiongo get github.com/miru-project/miru-core/pkg/extension/golang/sdkWrite and debug like a normal Go package
Section titled “Write and debug like a normal Go package”my.extension.go (same shape as in Detail Usage, just import sdk directly):
package myextension
import sdk "github.com/miru-project/miru-core/pkg/extension/golang/sdk"
func Search(pkg, kw string, page int, filter string) ([]sdk.ExtensionListItem, error) { // Debug it like any normal Go function — breakpoints, unit tests, etc. return []sdk.ExtensionListItem{{Title: kw, URL: "https://example.com/1"}}, nil}You can use gopls autocomplete, go build type-checking, and call entry functions like ordinary functions — exactly like any Go project.
Validate return shapes with go test
Section titled “Validate return shapes with go test”Add my.extension_test.go and confirm each entry function returns the right shape with go test:
package myextension
import "testing"
func TestSearch(t *testing.T) { items, err := Search("my.extension", "test", 1, "") if err != nil { t.Fatal(err) } if len(items) == 0 { t.Fatal("want at least one item") }}Debugging (Native Go)
Section titled “Debugging (Native Go)”Stage 1 is a normal Go package, so every standard Go debugging tool works — this is its biggest advantage.
Assertive debugging with go test
Section titled “Assertive debugging with go test”Don’t just eyeball the return value — encode your expectations in tests and use -v to see each case:
go test -v ./...go test -run TestSearch -v # run a single entry pointgo test -run TestDetail -v -count=1 # -count=1 disables caching, forces a rerunPrefer table-driven tests to cover many inputs (different page / kw / login-required):
func TestSearch(t *testing.T) { cases := []struct { name string kw string page int want int // minimum expected item count }{ {"normal keyword", "naruto", 1, 1}, {"empty result", "zzz_no_such_thing", 1, 0}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { items, err := Search("my.extension", c.kw, c.page, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(items) < c.want { t.Fatalf("want >= %d items, got %d", c.want, len(items)) } }) }}Printing and logging
Section titled “Printing and logging”- Quick prints:
fmt.Printf("DEBUG kw=%q items=%d\n", kw, len(items)); remove or switch tologonce confirmed. - For timestamps/line numbers:
import "log"thenlog.Printf(...). - Your
fmtoutput is not visible at Scriggo runtime (that’s host-side), so verify everything you need to see viago testin Stage 1.
Breakpoint debugging (Delve)
Section titled “Breakpoint debugging (Delve)”Install Delve, then break into your extension source directly:
dlv test -- -test.run TestSearch # breakpoint-debug inside a testdlv debug # if you have a main to debugIn VS Code / Zed, configure the Go debugger (built on Delve) for graphical stepping and variable inspection.
gopls and static checks
Section titled “gopls and static checks”goplsgives completion, jump-to-definition, and hover docs — type errors surface as you type.- Before submitting, run
go vet ./...andgo build ./...to catch low-level issues (e.g. unused variables) before they reach Scriggo runtime.
The pkg/extension/golang/extensions/example package inside miru-core is exactly this “one source, two stages” reference — it can be imported and called directly by native Go, and also compiled and run by Scriggo.