fix(brain): wing indexes and the link check read through brainfs (#157, slice 5a)
CI / Lint / Test / Vet (pull_request) Successful in 56s
CI / Mirror to GitHub (pull_request) Skipped

BuildWingIndex/BuildAllWingIndexes listed wiki/<wing>/<hall>/ with
os.ReadDir and opened each note by path: a symlinked note put the
target's title into _index.md (110k leaks in the swap race), a symlinked
wing was followed and its _index.md written into the target, and a FIFO
hung the build (it runs on every brain_write).

- New brainfs.ReadDirDirs: real subdirectories only, every component of
  the dir a real directory. Wings and halls are listed with it and
  ReadDirRegular; titles are read with ReadRegular
  (readTitleAndCreatedIn). A symlinked wing is an error; a symlinked hall
  is skipped.
- linkcheck entryIndex walks with WalkRegular: a symlinked .md name no
  longer makes a [[link]] resolve.
- readTitleAndCreated (path-based) stays for tunnel.DetectTunnels until
  PR #170 lands; that is slice 5b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 07:09:03 +02:00
co-authored by Claude Opus 5.5
parent b6ef11600b
commit 6e3c51c952
5 changed files with 213 additions and 54 deletions
+24 -17
View File
@@ -1,12 +1,15 @@
package brain
import (
"errors"
"fmt"
"os"
"path/filepath"
"io/fs"
"path"
"regexp"
"sort"
"strings"
"github.com/mathiasbq/hyperguild/ingestion/internal/brainfs"
)
// wikilinkRE matches [[target]] and [[target|Display Text]].
@@ -98,29 +101,33 @@ var linkedTrees = []string{"knowledge", "wiki"}
// one the audit will also resolve.
func entryIndex(brainDir string) (map[string]struct{}, error) {
index := map[string]struct{}{}
// Walked through the brain root (hyperguild#157): only regular files
// count as entries, so a symlinked .md name cannot make a link resolve.
bfs, err := brainfs.Open(brainDir)
if err != nil {
return nil, fmt.Errorf("open brain: %w", err)
}
defer func() { _ = bfs.Close() }()
for _, dir := range linkedTrees {
root := filepath.Join(brainDir, dir)
if _, err := os.Stat(root); os.IsNotExist(err) {
if _, err := bfs.Lstat(dir); errors.Is(err, fs.ErrNotExist) {
continue
} else if err != nil {
return nil, fmt.Errorf("stat %s: %w", dir, err)
}
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".md") {
var walkErr error
err := bfs.WalkRegular(dir, func(rel string) error {
if !strings.HasSuffix(rel, ".md") {
return nil
}
rel, err := filepath.Rel(brainDir, path)
if err != nil {
return err
}
rel = filepath.ToSlash(rel)
index[strings.TrimSuffix(filepath.Base(rel), ".md")] = struct{}{}
index[strings.TrimSuffix(path.Base(rel), ".md")] = struct{}{}
index[strings.TrimPrefix(strings.TrimSuffix(rel, ".md"), "wiki/")] = struct{}{}
return nil
}, func(rel string, err error) {
if walkErr == nil {
walkErr = fmt.Errorf("%s: %w", rel, err)
}
})
if err == nil {
err = walkErr
}
if err != nil {
return nil, fmt.Errorf("walk %s: %w", dir, err)
}