From 6e6969834375f6ee8cb34ca01fd8833f03ecfacd Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 17 Sep 2026 00:08:40 +0200 Subject: [PATCH] fix(brain): a one-word tunnel title must match the prose case-exactly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AutoTunnel matched note titles case-insensitively, so a one-word title linked every entry that happened to use that word. Measured on the live corpus: 185 entries carry an auto-written See-also link to wiki/telos/decisions/wrong.md (title "WRONG") and 136 to .../status.md — the two most-linked nodes in the whole graph, and both of them artifacts of the English language rather than references. telos's projects/problems/strategies/goals/beliefs entries account for another ~60 between them. That pollution is not cosmetic: inbound-link count is what the duplicate Fixer picks survivors by, what orphan detection reads, and what brain_graph shows. One-word titles now need the title's own casing in the prose ("WRONG", "BERT", "NLP" — an entity name in a sentence still links). Multi-word titles keep the case-insensitive match: "risk tier middleware" is a real mention however it is capitalised, and a three-word collision is not an accident. Co-Authored-By: Claude Opus 5 --- ingestion/internal/brain/tunnel.go | 26 ++++++++-- ingestion/internal/brain/tunnel_test.go | 68 +++++++++++++++++++++---- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/ingestion/internal/brain/tunnel.go b/ingestion/internal/brain/tunnel.go index 424ad9c..e1ed3e6 100644 --- a/ingestion/internal/brain/tunnel.go +++ b/ingestion/internal/brain/tunnel.go @@ -54,11 +54,24 @@ func DetectTunnels(brainDir, content string) ([]TunnelCandidate, error) { return nil } title, _ := readTitleAndCreated(path, strings.TrimSuffix(d.Name(), ".md")) - needle := strings.ToLower(strings.TrimSpace(title)) - if needle == "" { + title = strings.TrimSpace(title) + if title == "" { return nil } - idx := strings.Index(lowerContent, needle) + + // A one-word title has to match the prose case-exactly. Matched + // case-insensitively, it links every entry that happens to use the + // word: "WRONG" (wiki/telos/decisions/wrong.md) collected 185 inbound + // See-also links and "Status" 136, making the two most-linked nodes in + // the graph artifacts of the English language rather than references. + // Multi-word titles keep the case-insensitive match — "risk tier + // middleware" in prose is a real mention however it is capitalised, + // and a three-word collision is not an accident. + haystack, needle := lowerContent, strings.ToLower(title) + if isSingleWord(title) { + haystack, needle = content, title + } + idx := strings.Index(haystack, needle) if idx == -1 { return nil } @@ -69,7 +82,7 @@ func DetectTunnels(brainDir, content string) ([]TunnelCandidate, error) { out = append(out, TunnelCandidate{ TargetPath: filepath.ToSlash(rel), MatchedTerm: title, - Exact: isWholeWord(lowerContent, idx, len(needle)), + Exact: isWholeWord(haystack, idx, len(needle)), }) return nil }) @@ -79,6 +92,11 @@ func DetectTunnels(brainDir, content string) ([]TunnelCandidate, error) { return out, nil } +// isSingleWord reports whether title is one whitespace-free token. +func isSingleWord(title string) bool { + return !strings.ContainsAny(title, " \t") +} + // isWholeWord reports whether the substring at [idx, idx+n) in s is // bounded by non-alphanumeric characters (or string edges). func isWholeWord(s string, idx, n int) bool { diff --git a/ingestion/internal/brain/tunnel_test.go b/ingestion/internal/brain/tunnel_test.go index 2051982..6eccef3 100644 --- a/ingestion/internal/brain/tunnel_test.go +++ b/ingestion/internal/brain/tunnel_test.go @@ -102,16 +102,29 @@ func TestDetectTunnels_ExactTitleMatch(t *testing.T) { func TestDetectTunnels_FuzzyMatch(t *testing.T) { dir := t.TempDir() - seedNote(t, dir, "wiki/x/facts/routing.md", - "---\ntitle: Routing\n---\nbody.\n") + seedNote(t, dir, "wiki/x/facts/routing-table.md", + "---\ntitle: 'Routing Table'\n---\nbody.\n") // Substring of title appears in content, but not as a whole word. - candidates, err := brain.DetectTunnels(dir, "rerouting handles failover") + candidates, err := brain.DetectTunnels(dir, "the routing tables were rebuilt") require.NoError(t, err) require.Len(t, candidates, 1) assert.False(t, candidates[0].Exact, "substring-only match should be fuzzy") } +// A one-word title whose only appearance is lowercase inside another word is +// not even a candidate: it would fill the review file with English rather than +// with references. +func TestDetectTunnels_SingleWordSubstringIsNotACandidate(t *testing.T) { + dir := t.TempDir() + seedNote(t, dir, "wiki/x/facts/routing.md", + "---\ntitle: Routing\n---\nbody.\n") + + candidates, err := brain.DetectTunnels(dir, "rerouting handles failover") + require.NoError(t, err) + assert.Empty(t, candidates) +} + func TestDetectTunnels_NoFrontmatterFallsBackToSlug(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/x/facts/widget-flags.md", "# widget flags\n\nbody.\n") @@ -126,20 +139,21 @@ func TestDetectTunnels_NoFrontmatterFallsBackToSlug(t *testing.T) { func TestAutoTunnel_FuzzyGoesToCandidatesFile(t *testing.T) { dir := t.TempDir() - // Existing note in a different wing whose title is "Routing". - seedNote(t, dir, "wiki/other/facts/routing.md", - "---\nwing: other\nhall: facts\ntitle: Routing\n---\nbody.\n") - // Source note in another wing whose body mentions "rerouting" (substring match only). + // Existing note in a different wing whose title is "Routing Table". + seedNote(t, dir, "wiki/other/facts/routing-table.md", + "---\nwing: other\nhall: facts\ntitle: 'Routing Table'\n---\nbody.\n") + // Source note in another wing whose body mentions "routing tables" + // (substring match only — the title is not there as a whole word). seedNote(t, dir, "wiki/jepa-fx/facts/new.md", - "---\nwing: jepa-fx\nhall: facts\n---\nrerouting traffic\n") + "---\nwing: jepa-fx\nhall: facts\n---\nrouting tables rebuilt\n") require.NoError(t, brain.AutoTunnel(dir, - "wiki/jepa-fx/facts/new.md", "rerouting traffic")) + "wiki/jepa-fx/facts/new.md", "routing tables rebuilt")) // Source must not get auto-linked (fuzzy). got, err := os.ReadFile(filepath.Join(dir, "wiki/jepa-fx/facts/new.md")) require.NoError(t, err) - assert.NotContains(t, string(got), "[[other/facts/routing]]") + assert.NotContains(t, string(got), "[[other/facts/routing-table]]") // Candidates file must list the pair. matches, err := filepath.Glob(filepath.Join(dir, "raw", "tunnel-candidates-*.md")) @@ -148,7 +162,7 @@ func TestAutoTunnel_FuzzyGoesToCandidatesFile(t *testing.T) { body, err := os.ReadFile(matches[0]) require.NoError(t, err) assert.Contains(t, string(body), "wiki/jepa-fx/facts/new.md") - assert.Contains(t, string(body), "wiki/other/facts/routing.md") + assert.Contains(t, string(body), "wiki/other/facts/routing-table.md") assert.Contains(t, string(body), "Routing") } @@ -175,3 +189,35 @@ func TestWriteTunnel_AppendsToExistingSeeAlso(t *testing.T) { assert.Contains(t, s, "[[a/facts/old]]") assert.Contains(t, s, "[[b/facts/y]]") } + +// A one-word title matched case-insensitively links every entry that happens +// to use that word. Live cost before this rule: 185 entries auto-linked to +// wiki/telos/decisions/wrong.md (title "WRONG") and 136 to .../status.md, +// making the two most-linked nodes in the graph pure artifacts. +func TestDetectTunnels_SingleWordTitleNeedsAnExactCaseMatch(t *testing.T) { + dir := t.TempDir() + seedNote(t, dir, "wiki/telos/decisions/wrong.md", + "---\nwing: telos\nhall: decisions\ntitle: WRONG\n---\n# WRONG\n\nRetired beliefs.\n") + + got, err := brain.DetectTunnels(dir, "The obvious fix was to reject the write. Wrong here.") + require.NoError(t, err) + for _, c := range got { + assert.False(t, c.Exact, "a lowercase prose word must not auto-link a one-word title: %+v", c) + } + + got, err = brain.DetectTunnels(dir, "The beliefs listed under WRONG were retired.") + require.NoError(t, err) + require.Len(t, got, 1) + assert.True(t, got[0].Exact, "the title's own casing appearing in prose is a real reference") +} + +func TestDetectTunnels_MultiWordTitleStaysCaseInsensitive(t *testing.T) { + dir := t.TempDir() + seedNote(t, dir, "wiki/homelab/facts/risk-tier.md", + "---\nwing: homelab\nhall: facts\ntitle: 'Risk Tier Middleware'\n---\nbody\n") + + got, err := brain.DetectTunnels(dir, "We rebuilt the risk tier middleware last week.") + require.NoError(t, err) + require.Len(t, got, 1) + assert.True(t, got[0].Exact) +}