// ingestion/internal/wiki/inventory.go package wiki import ( "bufio" "bytes" "errors" "fmt" "io/fs" "sort" "strings" "github.com/mathiasbq/hyperguild/ingestion/internal/brainfs" ) // LoadInventory walks brain/wiki/ and returns all pages grouped by type. // Missing subdirectories are silently skipped. Pages are listed and read // through the brain root, regular files only (hyperguild#157); a type // directory that is a symlink is an error. func LoadInventory(brainDir string) (map[PageType][]Entry, error) { result := map[PageType][]Entry{ PageTypeConcept: {}, PageTypeEntity: {}, PageTypeSource: {}, } bfs, err := brainfs.Open(brainDir) if err != nil { return nil, fmt.Errorf("open brain: %w", err) } defer func() { _ = bfs.Close() }() for pt := range result { dir := "wiki/" + string(pt) names, err := bfs.ReadDirRegular(dir) if errors.Is(err, fs.ErrNotExist) { continue } if err != nil { return nil, fmt.Errorf("read dir %s: %w", dir, err) } for _, name := range names { if !strings.HasSuffix(name, ".md") { continue } slug := strings.TrimSuffix(name, ".md") title, aliases := readFrontmatter(bfs, dir+"/"+name, slug) result[pt] = append(result[pt], Entry{Slug: slug, Title: title, Aliases: aliases, Type: pt}) } } return result, nil } // readFrontmatter extracts title and aliases from YAML frontmatter. // Falls back to slug for title and empty aliases on any error. func readFrontmatter(bfs *brainfs.Brain, rel, fallbackSlug string) (title string, aliases []string) { title = fallbackSlug content, err := bfs.ReadRegular(rel) if err != nil { return } scanner := bufio.NewScanner(bytes.NewReader(content)) inFM := false inAliases := false for scanner.Scan() { line := scanner.Text() if strings.TrimSpace(line) == "---" { if !inFM { inFM = true continue } break // end of frontmatter } if !inFM { continue } // Detect alias list items (lines starting with " - "). if inAliases { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "- ") { aliases = append(aliases, strings.TrimPrefix(trimmed, "- ")) continue } inAliases = false // end of alias block } key, val, ok := strings.Cut(line, ":") if !ok { continue } switch strings.TrimSpace(key) { case "title": title = strings.Trim(strings.TrimSpace(val), `"'`) case "aliases": inAliases = true } } return } // LoadWingInventory returns what an ingest into one wing can match against: // that wing's sources (wiki//sources/) and the SHARED concepts and // entities (wiki/concepts/, wiki/entities/), which belong to no single wing // because their slug is the graph's global identity. Other wings' sources are // not read. func LoadWingInventory(brainDir, wing string) (map[PageType][]Entry, error) { result, err := LoadInventory(brainDir) if err != nil { return nil, err } result[PageTypeSource] = []Entry{} // the retired source layout is not this wing's bfs, err := brainfs.Open(brainDir) if err != nil { return nil, fmt.Errorf("open brain: %w", err) } defer func() { _ = bfs.Close() }() dir := "wiki/" + wing + "/sources" names, err := bfs.ReadDirRegular(dir) if errors.Is(err, fs.ErrNotExist) { return result, nil } if err != nil { return nil, fmt.Errorf("read dir %s: %w", dir, err) } for _, name := range names { if !strings.HasSuffix(name, ".md") { continue } rel := dir + "/" + name slug := strings.TrimSuffix(name, ".md") title, aliases := readFrontmatter(bfs, rel, slug) result[PageTypeSource] = append(result[PageTypeSource], Entry{Slug: slug, Title: title, Aliases: aliases, Type: PageTypeSource, Path: rel}) } return result, nil } // FindSlugs returns the brain-relative path of EVERY existing page whose // basename is slug, searching the retired dirs (wiki/{sources,concepts, // entities}/) and every wing hall (wiki///), sorted. The slug is a // page's graph identity, so two pages must not share one; returning all of // them (not the first) is what lets a caller tell "only my own page" from "my // own page and somebody else's". func FindSlugs(brainDir, slug string) ([]string, error) { bfs, err := brainfs.Open(brainDir) if err != nil { return nil, fmt.Errorf("open brain: %w", err) } defer func() { _ = bfs.Close() }() file := slug + ".md" var found []string check := func(dir string) error { names, err := bfs.ReadDirRegular(dir) if errors.Is(err, fs.ErrNotExist) { return nil } if err != nil { return err } for _, n := range names { if n == file { found = append(found, dir+"/"+file) } } return nil } for _, d := range []string{"wiki/sources", "wiki/concepts", "wiki/entities"} { if err := check(d); err != nil { return nil, err } } wings, err := bfs.ReadDirDirs("wiki") if errors.Is(err, fs.ErrNotExist) { return found, nil } if err != nil { return nil, err } for _, w := range wings { if w == "sources" || w == "concepts" || w == "entities" { continue } halls, err := bfs.ReadDirDirs("wiki/" + w) if err != nil { continue } for _, h := range halls { if err := check("wiki/" + w + "/" + h); err != nil { return nil, err } } } sort.Strings(found) return found, nil }