// ingestion/internal/wiki/inventory.go package wiki import ( "bufio" "bytes" "errors" "fmt" "io/fs" "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 }