// Package index defines the on-disk schema of the Einherjar framework index // and provides a loader for the embedded JSON blob. // // The index is built once at deploy time by cmd/indexer and consumed by every // MCP tool. Keeping it small, denormalised, and JSON-shaped means tools can // be implemented as straightforward in-memory filters. package index import ( "encoding/json" "fmt" "strings" "time" ) // SchemaVersion identifies the on-disk index format. Bump when fields change // in a way that breaks older consumers. const SchemaVersion = "einherjar.mcp/index/v1" // Index is the root of the embedded framework knowledge. type Index struct { Schema string `json:"schema"` Framework string `json:"framework"` BuiltAt time.Time `json:"builtAt"` Modules []Module `json:"modules"` } // Module describes one Einherjar module (e.g. core, web, auth-jwt). type Module struct { Name string `json:"name"` ImportPath string `json:"importPath"` Purpose string `json:"purpose"` Doc string `json:"doc,omitempty"` GoVersion string `json:"goVersion"` DependsOn []string `json:"dependsOn"` SubPackages []SubPackage `json:"subPackages"` Symbols []Symbol `json:"symbols"` ADRs []ADR `json:"adrs"` Examples []Example `json:"examples"` Compliance Compliance `json:"compliance"` Readme string `json:"readme,omitempty"` Changelog string `json:"changelog,omitempty"` } // Compliance captures a module's compliance_test.go contents: compile-time // interface assertions and the names of structural tests. It exists so an AI // assistant can know about machine-checked conventions before it writes code // that would violate them. type Compliance struct { InterfaceAsserts []InterfaceAssert `json:"interfaceAsserts"` Tests []ComplianceTest `json:"tests"` } // InterfaceAssert mirrors one `var _ Iface = impl` line in compliance_test.go. type InterfaceAssert struct { Module string `json:"module"` Interface string `json:"interface"` Impl string `json:"impl"` File string `json:"file"` Line int `json:"line"` } // ComplianceTest mirrors one Test* function in compliance_test.go. type ComplianceTest struct { Module string `json:"module"` Name string `json:"name"` Doc string `json:"doc"` File string `json:"file"` Line int `json:"line"` } // SubPackage is one importable sub-package of a module. type SubPackage struct { Name string `json:"name"` ImportPath string `json:"importPath"` Doc string `json:"doc"` } // Symbol is one exported declaration (type, func, interface, const, var). type Symbol struct { Module string `json:"module"` SubPackage string `json:"subPackage"` Kind string `json:"kind"` Name string `json:"name"` Signature string `json:"signature"` Doc string `json:"doc"` File string `json:"file"` Line int `json:"line"` } // ADR is one architectural decision record. type ADR struct { Module string `json:"module"` ID string `json:"id"` Title string `json:"title"` Body string `json:"body"` } // Example is a fenced code block lifted from a module README. type Example struct { Module string `json:"module"` SubPackage string `json:"subPackage"` Title string `json:"title"` Code string `json:"code"` Language string `json:"language"` } // Load parses the embedded JSON blob into an Index. It validates the schema // version and returns an empty (but non-nil) Index when the blob is the // placeholder shipped before the indexer has been run. func Load(raw []byte) (*Index, error) { if len(raw) == 0 { return &Index{Schema: SchemaVersion, Framework: "einherjar"}, nil } idx := &Index{} if err := json.Unmarshal(raw, idx); err != nil { return nil, fmt.Errorf("index: parse: %w", err) } if idx.Schema != "" && idx.Schema != SchemaVersion { return nil, fmt.Errorf("index: schema mismatch: got %q want %q", idx.Schema, SchemaVersion) } return idx, nil } // FindModule returns the module with the given name, or nil if absent. func (i *Index) FindModule(name string) *Module { for k := range i.Modules { if i.Modules[k].Name == name { return &i.Modules[k] } } return nil } // SearchSymbols returns up to limit symbols whose name or doc contains q // (case-insensitive). Module name and sub-package are also searched. func (i *Index) SearchSymbols(q string, limit int) []Symbol { if limit <= 0 { limit = 25 } needle := strings.ToLower(q) out := make([]Symbol, 0, limit) for _, m := range i.Modules { for _, s := range m.Symbols { if matches(s, needle) { out = append(out, s) if len(out) >= limit { return out } } } } return out } func matches(s Symbol, needle string) bool { if needle == "" { return true } return strings.Contains(strings.ToLower(s.Name), needle) || strings.Contains(strings.ToLower(s.Doc), needle) || strings.Contains(strings.ToLower(s.SubPackage), needle) || strings.Contains(strings.ToLower(s.Module), needle) }