selfsource

package module
v0.0.0-...-93267fe Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

selfsource: debuginfo for agents

Go Reference CI License

Embed a Go module's source, docs, and a build-time code index in the binary itself. The binary can then answer questions about its own code through a CLI, a web browser, MCP tools, or an AI chat loop.

Stack traces and pprof tell you where. Buildinfo tells you which build. Selfsource carries the what: the source text, doc comments, and cross-references for exactly that build.

Ask the binary about itself:

$ ./toy source chat
I'm github.com/oplehto/selfsource-go/examples/toy, AMA! (Ctrl-D to exit)
you> Where is the crash verb implemented, and what does it actually do?
  [search {"pattern":"crash"}]
  [read_file {"path":"main.go"}]
The crash verb is dispatched at main.go:63-64 and implemented by crash()
at main.go:78-82. It deliberately panics: g is a nil *Greeting and
g.Message() dereferences it ...

Or hand it its own crash:

$ ./toy crash 2>&1 | ./toy source report
binary   vcs.revision 95ba829... — MATCHES bundle
#0 main.(*Greeting).Message
   greet.go:11
       10 | func (g *Greeting) Message() string {
   >   11 | 	return "Hello, " + g.Name + "!"
   doc: Message returns the greeting text. ...

No repo checkout needed. And no guessing whether the source matches the binary: the bundle records the git revision it was generated at, and every surface checks it against the binary's own debug.ReadBuildInfo().

Quick start

go install github.com/oplehto/selfsource-go/cmd/selfsource@latest   # the CLI
go get github.com/oplehto/selfsource-go                             # the library

Integration is three pieces: generate, embed, expose.

// 1. at build time:  selfsource gen -root . -o bundle/app.selfsource

// 2. embed it (bundle/.gitkeep committed, the blob gitignored,
//    so a bare `go build` compiles before the bundle exists)
//go:embed all:bundle
var bundleFS embed.FS

func init() { auto.Enable(bundleFS, "bundle/app.selfsource") }   // 3a. always-on

case "source":                                                   // 3b. explicit verb
    r, _ := selfsource.Decode(mustRead(bundleFS, "bundle/app.selfsource"))
    return cli.Run(r, os.Args[2:])

examples/toy is the reference integration. make demo runs its whole flow: generate, build, crash, annotated self-report. docs/case-studies/ walks the same integration through three real programs — scc, hey, and croc — with captured output, each contrasted against doing the same job without selfsource: profiles that confirm a doc comment's hot-path claim versus pprof's source-path scavenger hunt, a live goroutine dump annotated back to source versus reading it by hand, and the secret scanner earning its keep.

A missing bundle never breaks the host. Every surface reports "no bundle embedded" instead.

The surfaces

cli.Run mounts the full verb set under one subcommand. The standalone CLI reaches the same verbs with selfsource -b app.selfsource <verb>.

Verb Does
info Manifest, counts, binary-vs-bundle revision check
ls / cat / grep List, read (loose path match), regexp search
sym / refs / deps [-r] Symbol detail, reference sites, import graph
extract [-o dir] Write the bundled tree to disk, for pprof -source_path, diffing, editors
report [-C n] Annotate a stack trace from stdin with source and docs
profile Analyze pprof profiles against the embedded source: top functions, per-line listings, diffs, self-collection
serve HTTP source browser; loopback default, -token required elsewhere
mcp The same queries as MCP tools over stdio, plus workflow prompts
chat Agent loop against your LLM endpoint (-api-key, -base-url, -model / $SELFSOURCE_MODEL)
skill Emit an agent skill (SKILL.md) for driving this binary, with its real path filled in
Profiles

The profile verb reads pprof profiles — the format runtime/pprof and net/http/pprof produce — and joins the samples against the bundle, so the hot paths come back with symbol kinds, signatures, doc comments, and annotated source lines instead of bare function names:

profile -cpu <file>           top functions, joined to the bundle
profile -heap <file>          same, for heap profiles
profile -collect <duration>   CPU-profile THIS process, then analyze
profile -cpu <file> -list <func>   pprof-style annotated listing, no -source_path
profile -diff <a> <b>         normalized per-function delta, regression triage

The decoder is hand-rolled stdlib (gzip plus the profile.proto subset Go emits) — the no-dependencies rule holds. Frames join by fully-qualified function name against the symbol index first; file-path suffix matching is the fallback, with -trimpath builds as the happy path, and a path match is only trusted when the file actually declares the function. Profiles carrying a GNU build ID are checked against the running binary and a mismatch warns loudly; on platforms where profiles carry no build ID (most), the existing bundle-vs-binary revision check is the identity story. make demo-profile shows the flagship loop: the selfsource CLI, carrying its own source, profiles itself and explains its own hot paths. The same queries are MCP tools (hot_functions, annotate_frame, profile_diff, collect_profile), response-capped so an agent asks narrow questions instead of drowning in samples.

Prompts and skills

The binary carries its own debugging expertise, not just tools. The MCP server advertises four workflow playbooks as prompts — diagnose_crash, hot_paths, perf_regression, orient — which MCP clients surface as slash commands; each one walks an agent through the right tool sequence, starting with the identity check. The chat verb folds the same discipline into its system prompt, and both frontends now share one tool set (the MCP server's), including annotate_stack for crash triage and the profile tools.

skill goes the other direction: the binary writes an agent skill describing itself —

./yourapp source skill -o ~/.claude/skills/yourapp-debug

— a ready-to-use SKILL.md with the binary's real path, verbs, and workflows, so a coding agent on the same machine knows how to interrogate the deployed build without being told.

auto.Enable is the net/http/pprof of selfsource. One call in init() gives every build three always-on surfaces:

  • /debug/source/ on http.DefaultServeMux. The browser is live wherever the host already serves its debug mux.
  • SELFSOURCE_MCP=1 ./yourapp serves MCP over stdio instead of running the app. Any integrated binary drops into an MCP client config as-is.
  • SELFSOURCE_SERVE=<addr> ./yourapp runs the browser standalone. Loopback only, unless SELFSOURCE_TOKEN is set.

Everything is compiled in either way; the environment picks what is live. SELFSOURCE=off is an operator kill switch that keeps every surface dormant (the bundle is not even decoded). auto.EnableOptIn is the inverse posture: zero always-on debug surface until SELFSOURCE=1 or one of the activation variables asks — for production binaries that want the capability one env var away instead of always mounted. Gating governs runtime exposure only: the bundle still ships inside the binary, and anyone holding the file can extract it.

The browser looks like this (/debug/source/, or serve, or SELFSOURCE_SERVE):

Every surface is read-only by construction. HTML output is escaped. The browser refuses non-loopback binds without bearer auth. MCP is stdio-only, so the ability to spawn the process is the access control. Chat talks only to the endpoint you point it at.

What goes into a bundle

selfsource gen -root . -o app.selfsource produces a deterministic blob. Generation at the same commit is byte-identical. Revision and build time default to the current git commit, with a warning on a dirty tree (a dirty bundle matches no commit).

Three things ship: files, the index, and the manifest.

Files. The default is a narrow allowlist: **/*.go, go.mod, build files, the license, and published docs by name (README, CHANGELOG, and so on). Deliberately not **/*.md, which would sweep up TODO.md and internal notes.

To customize, drop a .selfsource file in the module root. It works like gitignore: one glob per line, ! excludes, # comments.

# what goes into the bundle
**/*.go
**/*.md
go.mod
!vendor/**
!**/testdata/**

Generation prints what shipped and paste-ready globs for everything it skipped (-list / -list-skipped). Widening the list is a copy-paste, not archaeology.

The index. Always covers the whole module, whatever the file globs say. Every package-scope symbol, exported or not, including interface and aliased-type methods: kind, signature, doc comment, definition, and all reference sites as file:line:col (test files included). Plus the package import graph, both directions.

The index is computed by the same type checker as the build, so it cannot drift from the code. It can only age with the binary, which is what you want from debuginfo. Unexported symbols matter because panic frames usually are.

The manifest. Module path, revision, build time, Go toolchain, and the resolved dependency list (path@version).

What never ships

Four safety mechanisms hold regardless of your globs:

  • Dot directories are never descended (.git, .aws, .ssh, ...).
  • Symlinks are never followed. A link named notes.go pointing at ~/.aws/credentials is skipped, not read.
  • Credential-shaped paths never ship (.env, *.pem, *.key, id_rsa, *.tfstate, kubeconfig, ...) unless you pass -unsafe-include-all.
  • Included content is scanned for credential shapes: cloud and API keys, private key blocks, tokens in URLs. A finding aborts generation with masked excerpts. Override per line with a selfsource:allow-secret comment, or wholesale with -allow-secrets.

The build host never leaks either. No absolute paths, user names, or GOPATH appear anywhere in a bundle; TestNoHostPathsLeak holds the pipeline to it.

Design

Everything is a library function. The binaries are thin wrappers, and a host picks the pieces it wants:

Package Provides
selfsource (root) DecodeReader: files, search, symbols, references, import graph; ExecTool, AnnotateStack, Extract
gen The build-time generator (the one place golang.org/x/tools is used)
cli Run(r, args), the verb set above
browser The HTTP browser, separate so hosts that only query never link net/http
mcpserver The MCP server: a ~200-line stdio JSON-RPC implementation, no SDK
auto The init()-time integration
cmd/selfsource Standalone CLI: gen plus -b bundle <verb>

Every runtime piece is stdlib-only. The chat client is one HTTP POST. The module's single dependency (golang.org/x/tools) belongs to the generator and is never linked into a host.

Measured on the toy: a reader-only host is about 3.6 MB, of which 2.5 MB is the Go runtime any binary carries (the reader itself adds about 1.1 MB). The everything-integration is about 12.8 MB.

Why

An operator, or an agent, has only the deployed binary and a panic from its logs. No repo, and no doubt which commit:

  1. ./app crash-log | ./app source report resolves every frame to source and docs from the build that crashed.
  2. ./app source sym Foo / refs Foo explores the implicated code paths.
  3. pprof says something is hot? ./app source profile -cpu cpu.pb names the hot functions with their docs and definition sites, and -list <func> is the annotated per-line listing — straight from the binary, no external tooling. (d=$(./app source extract) && go tool pprof -source_path="$d" ... still works as the fallback when you want pprof's own UI.)

Prior art: zipizap/EmbeddedSource (GPL-3.0) embeds a directory of .go files and extracts them at runtime. The extract verb here owes it the reminder that plain files on disk are what existing tools consume. Selfsource adds the code index, deep trees via glob config, the identity check, and the browser/MCP/chat/report surfaces.

Development

make check    # fast gate: gofmt + vet + tests
make ci       # everything CI runs: check + race + staticcheck + govulncheck + fuzz smoke + example
make example  # generate the toy's bundle and build the toy
make demo     # example + crash → annotated self-report

Pre-1.0, the API may still move between minor versions; changes are called out in CHANGELOG.md. The bundle format is versioned independently (selfsource/v1 in the manifest). Readers reject formats they don't know rather than guessing.

License

Apache License 2.0; see LICENSE and NOTICE. Contributions are accepted under the same license (Apache-2.0 § 5, inbound = outbound). No CLA, no sign-off ceremony.

Documentation

Overview

Package selfsource packages a Go module's source, docs, and a build-time code index into one deterministic blob a binary can embed and serve — debuginfo for agents.

A bundle is a gzip'd tar archive with three kinds of entries:

manifest.json   build identity (module path, VCS revision, build time)
index.json      code index (packages, exported symbols, references)
files/<path>    verbatim source and doc files, module-relative paths

Encoding is byte-deterministic: same inputs produce the same blob.

Index

Examples

Constants

View Source
const DefaultMaxDecodedBytes = 512 << 20 // 512 MiB

DefaultMaxDecodedBytes caps the total decompressed size Decode will accept. A bundle is a compressed archive, so a small blob can expand enormously; reading a bundle you did not build is a documented workflow, and an unbounded reader would make that an out-of-memory vector.

View Source
const FormatV1 = "selfsource/v1"

FormatV1 identifies the first bundle format version.

Variables

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is wrapped by lookups that matched nothing.

View Source
var ErrTooLarge = errors.New("selfsource: bundle expands beyond the decode limit; raise it with DecodeLimit if the bundle is trusted")

ErrTooLarge reports a bundle whose decompressed size exceeds the budget.

Functions

func MatchPath

func MatchPath(pattern, p string) bool

MatchPath reports whether a slash-separated path matches a glob pattern. Patterns use path.Match per segment, plus "**" matching any number of segments (including zero). Patterns are anchored: "*.go" matches only root-level files; use "**/*.go" for the whole tree.

func SplitLines

func SplitLines(content []byte) []string

SplitLines splits file content into lines without the phantom trailing empty line that strings.Split yields for newline-terminated content.

func SplitRef

func SplitRef(ref string) (path, line, col string)

SplitRef splits a "file:line" or "file:line:col" reference into its parts; line and col are empty when absent.

Types

type AmbiguousError

type AmbiguousError struct {
	Query      string
	Candidates []string
}

AmbiguousError reports a loose lookup that matched more than one candidate.

func (*AmbiguousError) Error

func (e *AmbiguousError) Error() string

type Bundle

type Bundle struct {
	Manifest Manifest
	Index    Index
	Files    map[string][]byte // module-relative slash paths
}

Bundle is the in-memory form, pre-encoding.

func (*Bundle) Encode

func (b *Bundle) Encode() ([]byte, error)

Encode serializes the bundle deterministically.

type Frame

type Frame struct {
	Func string
	File string // as printed in the trace (usually absolute build path)
	Line int
}

Frame is one resolved stack-trace frame.

func ParseStack

func ParseStack(trace []byte) []Frame

ParseStack extracts frames from a Go panic message or goroutine dump. Unrecognized lines are skipped, so raw crash output can be fed verbatim.

Example
package main

import (
	"fmt"

	"github.com/oplehto/selfsource-go"
)

func main() {
	trace := []byte(`panic: runtime error: invalid memory address or nil pointer dereference

goroutine 1 [running]:
example.com/demo.Greet(...)
	/build/demo.go:4 +0x18
main.main()
	/build/main.go:10 +0x2c
`)
	for _, f := range selfsource.ParseStack(trace) {
		fmt.Printf("%s at %s:%d\n", f.Func, f.File, f.Line)
	}
}
Output:
example.com/demo.Greet at /build/demo.go:4
main.main at /build/main.go:10

type Index

type Index struct {
	Packages []Package `json:"packages"`
	Symbols  []Symbol  `json:"symbols"`
}

Index is the build-time code index — the "key gopls output".

type Manifest

type Manifest struct {
	Format     string `json:"format"`
	ModulePath string `json:"module_path"`
	Revision   string `json:"revision"`
	BuildTime  string `json:"build_time"`
	// GoVersion is the toolchain that generated the index (e.g. "go1.26.0").
	GoVersion string `json:"go_version,omitempty"`
	// Deps lists resolved module requirements, sorted by path. Same
	// disclosure as go.sum — exclude via config if that matters to you.
	Deps []ModuleDep `json:"deps,omitempty"`
}

Manifest records the identity of the build a bundle was generated from.

Every field is deliberately host-independent: no filesystem paths, user names, or environment values. A bundle ships inside a binary that may be handed to anyone, so it describes the CODE, never the machine that compiled it. TestNoHostPathsLeak enforces this end to end.

type Match

type Match struct {
	Path string
	Line int
	Text string
}

Match is one search hit.

type ModuleDep

type ModuleDep struct {
	Path    string `json:"path"`
	Version string `json:"version"`
}

ModuleDep is one resolved module requirement. Deliberately carries no local directory: that would be a host path.

type Package

type Package struct {
	ImportPath string   `json:"import_path"`
	Dir        string   `json:"dir"`
	Files      []string `json:"files"`
	Imports    []string `json:"imports"`
}

Package is one module-local package in the index.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader answers queries over a decoded bundle.

func Decode

func Decode(data []byte) (*Reader, error)

Decode parses an encoded bundle, accepting at most DefaultMaxDecodedBytes of decompressed content.

Example
package main

import (
	"fmt"

	"github.com/oplehto/selfsource-go"
)

// exampleBundle builds a tiny two-file bundle in memory. Real bundles come
// from the gen package (or `selfsource gen`) at build time and reach the
// binary via go:embed.
func exampleBundle() *selfsource.Reader {
	b := &selfsource.Bundle{
		Manifest: selfsource.Manifest{
			Format:     selfsource.FormatV1,
			ModulePath: "example.com/demo",
			Revision:   "0123abc",
			BuildTime:  "2026-01-01T00:00:00Z",
		},
		Index: selfsource.Index{
			Packages: []selfsource.Package{
				{ImportPath: "example.com/demo", Dir: ".", Files: []string{"demo.go"}, Imports: []string{"fmt"}},
			},
			Symbols: []selfsource.Symbol{
				{
					Package: "example.com/demo", Name: "Greet", Kind: "func",
					Signature: "func Greet(name string) string",
					Doc:       "Greet says hello.",
					Def:       "demo.go:4",
					Refs:      []string{"demo_test.go:9:8"},
					Exported:  true,
				},
			},
		},
		Files: map[string][]byte{
			"demo.go":   []byte("package demo\n\n// Greet says hello.\nfunc Greet(name string) string { return \"Hello, \" + name }\n"),
			"README.md": []byte("# demo\n"),
		},
	}
	data, err := b.Encode()
	if err != nil {
		panic(err)
	}
	r, err := selfsource.Decode(data)
	if err != nil {
		panic(err)
	}
	return r
}

func main() {
	r := exampleBundle()
	m := r.Manifest()
	fmt.Println(m.ModulePath, "at", m.Revision)
	fmt.Println(r.Files())
}
Output:
example.com/demo at 0123abc
[README.md demo.go]

func DecodeLimit

func DecodeLimit(data []byte, maxBytes int64) (*Reader, error)

DecodeLimit is Decode with an explicit decompressed-size budget. A limit of zero or less means unlimited — only for bundles you produced yourself.

func (*Reader) AnnotateStack

func (r *Reader) AnnotateStack(trace []byte, contextLines int) (string, error)

AnnotateStack renders a stack trace as a report: each frame that resolves into the bundle is shown with contextLines of surrounding source and, when the function is indexed, its doc comment. Frames outside the bundle (runtime, stdlib, dependencies) are listed but marked as such.

func (*Reader) Deps

func (r *Reader) Deps(name string) ([]string, error)

Deps returns a package's direct imports.

func (*Reader) ExecTool

func (r *Reader) ExecTool(name string, input []byte) (string, error)

ExecTool runs one named bundle query with a JSON input object — the shared executor behind the CLI chat loop and the MCP server, exported so host binaries can wire the same surface into their own agent frontends.

Tools and inputs: info {}, list_files {}, read_file {path}, search {pattern, glob?}, symbol {name}, references {name}, deps {package, reverse?}, annotate_stack {trace, context?}.

func (*Reader) Extract

func (r *Reader) Extract(dir string) error

Extract writes the bundle's files into dir, recreating the module-relative layout — for tools that need source on disk (go tool pprof -source_path, diffing against a checkout, rebuilding). The destination must not exist or must be an empty directory, and must not be a symlink: Extract never overwrites existing content and never follows a link out of the path you named.

func (*Reader) Files

func (r *Reader) Files() []string

Files returns all bundled file paths, sorted.

func (*Reader) Importers

func (r *Reader) Importers(name string) ([]string, error)

Importers returns the module-local packages that directly import name.

func (*Reader) Manifest

func (r *Reader) Manifest() Manifest

Manifest returns the bundle's build identity.

func (*Reader) Package

func (r *Reader) Package(name string) (Package, error)

Package resolves an import path loosely: exact, or unique path suffix.

func (*Reader) Packages

func (r *Reader) Packages() []Package

Packages returns the indexed packages, sorted by import path.

func (*Reader) Read

func (r *Reader) Read(name string) (string, []byte, error)

Read returns a file's resolved path and content. The name may be the exact path, a path suffix, or a bare basename; an ambiguous name is an error.

Example
package main

import (
	"fmt"

	"github.com/oplehto/selfsource-go"
)

// exampleBundle builds a tiny two-file bundle in memory. Real bundles come
// from the gen package (or `selfsource gen`) at build time and reach the
// binary via go:embed.
func exampleBundle() *selfsource.Reader {
	b := &selfsource.Bundle{
		Manifest: selfsource.Manifest{
			Format:     selfsource.FormatV1,
			ModulePath: "example.com/demo",
			Revision:   "0123abc",
			BuildTime:  "2026-01-01T00:00:00Z",
		},
		Index: selfsource.Index{
			Packages: []selfsource.Package{
				{ImportPath: "example.com/demo", Dir: ".", Files: []string{"demo.go"}, Imports: []string{"fmt"}},
			},
			Symbols: []selfsource.Symbol{
				{
					Package: "example.com/demo", Name: "Greet", Kind: "func",
					Signature: "func Greet(name string) string",
					Doc:       "Greet says hello.",
					Def:       "demo.go:4",
					Refs:      []string{"demo_test.go:9:8"},
					Exported:  true,
				},
			},
		},
		Files: map[string][]byte{
			"demo.go":   []byte("package demo\n\n// Greet says hello.\nfunc Greet(name string) string { return \"Hello, \" + name }\n"),
			"README.md": []byte("# demo\n"),
		},
	}
	data, err := b.Encode()
	if err != nil {
		panic(err)
	}
	r, err := selfsource.Decode(data)
	if err != nil {
		panic(err)
	}
	return r
}

func main() {
	r := exampleBundle()
	// Loose matching: a bare basename resolves when unique.
	path, content, err := r.Read("demo.go")
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s is %d bytes\n", path, len(content))
}
Output:
demo.go is 94 bytes

func (*Reader) ResolveTracePath

func (r *Reader) ResolveTracePath(file string) (string, bool)

ResolveTracePath maps a file path as the runtime prints it — an absolute build-machine path, or a module-relative one under -trimpath — onto a bundled path. The shared join fallback for stack reports and pprof frames.

func (*Reader) Search

func (r *Reader) Search(pattern string, pathGlobs []string) ([]Match, error)

Search runs a regexp over bundled files. Empty pathGlobs means all files; otherwise a file is searched when any glob matches its path. A glob set that matches no file at all is an error rather than an empty result — a silently-empty search reads as "no matches" and hides the typo.

Example
package main

import (
	"fmt"

	"github.com/oplehto/selfsource-go"
)

// exampleBundle builds a tiny two-file bundle in memory. Real bundles come
// from the gen package (or `selfsource gen`) at build time and reach the
// binary via go:embed.
func exampleBundle() *selfsource.Reader {
	b := &selfsource.Bundle{
		Manifest: selfsource.Manifest{
			Format:     selfsource.FormatV1,
			ModulePath: "example.com/demo",
			Revision:   "0123abc",
			BuildTime:  "2026-01-01T00:00:00Z",
		},
		Index: selfsource.Index{
			Packages: []selfsource.Package{
				{ImportPath: "example.com/demo", Dir: ".", Files: []string{"demo.go"}, Imports: []string{"fmt"}},
			},
			Symbols: []selfsource.Symbol{
				{
					Package: "example.com/demo", Name: "Greet", Kind: "func",
					Signature: "func Greet(name string) string",
					Doc:       "Greet says hello.",
					Def:       "demo.go:4",
					Refs:      []string{"demo_test.go:9:8"},
					Exported:  true,
				},
			},
		},
		Files: map[string][]byte{
			"demo.go":   []byte("package demo\n\n// Greet says hello.\nfunc Greet(name string) string { return \"Hello, \" + name }\n"),
			"README.md": []byte("# demo\n"),
		},
	}
	data, err := b.Encode()
	if err != nil {
		panic(err)
	}
	r, err := selfsource.Decode(data)
	if err != nil {
		panic(err)
	}
	return r
}

func main() {
	r := exampleBundle()
	matches, err := r.Search(`func \w+`, []string{"**/*.go"})
	if err != nil {
		panic(err)
	}
	for _, m := range matches {
		fmt.Printf("%s:%d: %s\n", m.Path, m.Line, m.Text)
	}
}
Output:
demo.go:4: func Greet(name string) string { return "Hello, " + name }

func (*Reader) Symbol

func (r *Reader) Symbol(name string) (Symbol, error)

Symbol resolves a symbol loosely: "pkg.Name", "pkgsuffix.Name", "Name", or "Type.Method" forms all work when unique.

Example
package main

import (
	"fmt"

	"github.com/oplehto/selfsource-go"
)

// exampleBundle builds a tiny two-file bundle in memory. Real bundles come
// from the gen package (or `selfsource gen`) at build time and reach the
// binary via go:embed.
func exampleBundle() *selfsource.Reader {
	b := &selfsource.Bundle{
		Manifest: selfsource.Manifest{
			Format:     selfsource.FormatV1,
			ModulePath: "example.com/demo",
			Revision:   "0123abc",
			BuildTime:  "2026-01-01T00:00:00Z",
		},
		Index: selfsource.Index{
			Packages: []selfsource.Package{
				{ImportPath: "example.com/demo", Dir: ".", Files: []string{"demo.go"}, Imports: []string{"fmt"}},
			},
			Symbols: []selfsource.Symbol{
				{
					Package: "example.com/demo", Name: "Greet", Kind: "func",
					Signature: "func Greet(name string) string",
					Doc:       "Greet says hello.",
					Def:       "demo.go:4",
					Refs:      []string{"demo_test.go:9:8"},
					Exported:  true,
				},
			},
		},
		Files: map[string][]byte{
			"demo.go":   []byte("package demo\n\n// Greet says hello.\nfunc Greet(name string) string { return \"Hello, \" + name }\n"),
			"README.md": []byte("# demo\n"),
		},
	}
	data, err := b.Encode()
	if err != nil {
		panic(err)
	}
	r, err := selfsource.Decode(data)
	if err != nil {
		panic(err)
	}
	return r
}

func main() {
	r := exampleBundle()
	sym, err := r.Symbol("Greet")
	if err != nil {
		panic(err)
	}
	fmt.Println(sym.Signature)
	fmt.Println("defined at", sym.Def, "referenced from", sym.Refs[0])
}
Output:
func Greet(name string) string
defined at demo.go:4 referenced from demo_test.go:9:8

func (*Reader) SymbolForFrame

func (r *Reader) SymbolForFrame(fn string) (Symbol, error)

SymbolForFrame resolves a runtime-style function name — the form stack traces and pprof profiles print, like "pkg/path.(*Type).Method", "pkg.Func[go.shape.int]", or "pkg.Func.func1" — to its indexed symbol. The primary join key between runtime data and the bundle.

The frame's package part constrains the match: "internal/stringslite.Index" must NOT resolve to a module type that happens to be named Index. Loose bare-name lookups are Reader.Symbol's job, for humans; frames carry a package and it is checked.

func (*Reader) Symbols

func (r *Reader) Symbols() []Symbol

Symbols returns the indexed symbols, sorted by (package, name).

type Symbol

type Symbol struct {
	Package   string   `json:"package"` // full import path
	Name      string   `json:"name"`    // "Name" or "Type.Method"
	Kind      string   `json:"kind"`    // func, method, type, const, var
	Signature string   `json:"signature"`
	Doc       string   `json:"doc,omitempty"`
	Def       string   `json:"def"`                // file:line, bundle-relative
	Refs      []string `json:"refs,omitempty"`     // file:line:col sites, sorted
	Exported  bool     `json:"exported,omitempty"` // visible outside its package
}

Symbol is one package-scope symbol, or a method of a named or interface type. Unexported symbols are indexed too: a panic's frames are usually unexported, and that is exactly when an offline index earns its place.

Directories

Path Synopsis
Package auto is the init()-time integration — the net/http/pprof of selfsource.
Package auto is the init()-time integration — the net/http/pprof of selfsource.
Package browser serves a read-only HTTP source browser over a bundle — a pprof-style self-serve UI.
Package browser serves a read-only HTTP source browser over a bundle — a pprof-style self-serve UI.
chat: an agent loop over the bundle — "I'm the binary, ask me anything".
chat: an agent loop over the bundle — "I'm the binary, ask me anything".
cmd
selfsource command
Command selfsource generates and inspects source bundles.
Command selfsource generates and inspects source bundles.
Package gen generates a selfsource from a module tree at build time.
Package gen generates a selfsource from a module tree at build time.
Package mcpserver exposes a bundle's query surface as MCP tools, for embedding in a host binary (an always-on `yourapp source mcp` verb) or via the selfsource CLI.
Package mcpserver exposes a bundle's query surface as MCP tools, for embedding in a host binary (an always-on `yourapp source mcp` verb) or via the selfsource CLI.
Package profile decodes pprof profiles and joins them against an embedded selfsource bundle, so a binary that carries its own source can explain its own hot paths — no repo checkout, no `go tool pprof -source_path`.
Package profile decodes pprof profiles and joins them against an embedded selfsource bundle, so a binary that carries its own source can explain its own hot paths — no repo checkout, no `go tool pprof -source_path`.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL