diff --git a/src/core/link-extraction.ts b/src/core/link-extraction.ts index a23f2221e..6ff2f6822 100644 --- a/src/core/link-extraction.ts +++ b/src/core/link-extraction.ts @@ -567,7 +567,7 @@ export async function extractPageLinks( // path needed `resolveBasenameMatches` on the real resolver. let fmUnresolved: UnresolvedFrontmatterRef[] = []; if (!opts.skipFrontmatter) { - const fm = await extractFrontmatterLinks(slug, pageType, frontmatter, resolver, opts.globalBasename); + const fm = await extractFrontmatterLinks(slug, pageType, frontmatter, resolver); candidates.push(...fm.candidates); fmUnresolved = fm.unresolved; } @@ -1042,24 +1042,11 @@ export interface FrontmatterExtractResult { * Arrays of objects: uses the `name` or `slug` property (codex tension 6.3). * Non-string / non-object entries: silently skipped (log-only). */ -/** - * Unwrap an Obsidian-style `[[wikilink]]` frontmatter value to its bare link - * target: drops the surrounding brackets, any `|alias`, and `#heading` / - * `^block` suffixes. Non-bracketed values pass through unchanged. Used to feed - * the basename index when global_basename is on (see extractFrontmatterLinks). - */ -function unwrapWikilink(value: string): string { - const match = /^\s*\[\[(.+?)\]\]\s*$/.exec(value); - if (!match) return value; - return match[1].split('|')[0].split('#')[0].split('^')[0].trim(); -} - export async function extractFrontmatterLinks( slug: string, pageType: PageType, frontmatter: Record, resolver: SlugResolver, - globalBasename = false, ): Promise { const candidates: LinkCandidate[] = []; const unresolved: UnresolvedFrontmatterRef[] = []; @@ -1092,23 +1079,7 @@ export async function extractFrontmatterLinks( } if (!name) continue; // skip numbers, nulls, malformed objects - let resolved = await resolver.resolve(name, mapping.dirHint); - if (!resolved && globalBasename && typeof resolver.resolveBasenameMatches === 'function') { - // Issue #972 follow-up: extend global_basename resolution to - // frontmatter link fields. resolve() can't reach a bare-title - // wikilink value (e.g. `sources: "[[2025-12-25_mentor-extraction]]"`) - // — it has no '/', so the slug-direct getPage is skipped, and the - // field's dirHint may name folders that don't exist in this brain, - // so the dir-scoped exact + fuzzy steps miss too. When - // link_resolution.global_basename is on, fall back to the SAME - // basename index the body bare-wikilink pass uses, after unwrapping - // the brackets. Unique-match-only: ambiguous basenames (e.g. archive - // duplicates, generic hubs like `_index`) stay unresolved rather than - // create a wrong edge. - const matches = (await resolver.resolveBasenameMatches(unwrapWikilink(name))) - .filter((s) => s !== slug); - if (matches.length === 1) resolved = matches[0]; - } + const resolved = await resolver.resolve(name, mapping.dirHint); if (!resolved) { unresolved.push({ field, name }); continue; diff --git a/test/link-extraction.test.ts b/test/link-extraction.test.ts index 8f7564edd..9a2bc4f7d 100644 --- a/test/link-extraction.test.ts +++ b/test/link-extraction.test.ts @@ -270,53 +270,6 @@ describe('extractPageLinks', () => { expect(sourceLink!.targetSlug).toBe('meetings/2026-01-15'); }); - // ─── global_basename for frontmatter link fields (issue #972 follow-up) ─── - - test('frontmatter [[wikilink]] resolves via global_basename when resolve() misses', async () => { - // `sources: [[2025-12-25_mentor-extraction]]` — bare title, no '/', so the - // standard resolver misses; the basename index finds the single match. - const resolver: SlugResolver = { - resolve: async () => null, - resolveBasenameMatches: async (name) => - name === '2025-12-25_mentor-extraction' - ? ['trading/raw/2025-12-25_mentor-extraction'] - : [], - }; - const { candidates } = await extractPageLinks( - 'trading/wiki/backtesting', 'Body.', - { sources: ['[[2025-12-25_mentor-extraction]]'] }, - 'concept', resolver, { globalBasename: true }, - ); - // `sources` is direction:'incoming' → edge is resolved → page. - const edge = candidates.find(c => c.linkType === 'discussed_in'); - expect(edge).toBeDefined(); - expect(edge!.fromSlug).toBe('trading/raw/2025-12-25_mentor-extraction'); - expect(edge!.targetSlug).toBe('trading/wiki/backtesting'); - }); - - test('frontmatter basename fallback stays unresolved when ambiguous (>1 match)', async () => { - const resolver: SlugResolver = { - resolve: async () => null, - resolveBasenameMatches: async () => ['a/dup', 'b/dup'], - }; - const { candidates, unresolved } = await extractPageLinks( - 'wiki/x', 'Body.', { sources: ['[[dup]]'] }, 'concept', resolver, { globalBasename: true }, - ); - expect(candidates.find(c => c.linkType === 'discussed_in')).toBeUndefined(); - expect(unresolved.some(u => u.field === 'sources')).toBe(true); - }); - - test('frontmatter basename fallback is gated OFF when globalBasename is false', async () => { - const resolver: SlugResolver = { - resolve: async () => null, - resolveBasenameMatches: async () => ['raw/note'], - }; - const { candidates } = await extractPageLinks( - 'wiki/x', 'Body.', { sources: ['[[note]]'] }, 'concept', resolver, // globalBasename omitted = false - ); - expect(candidates.find(c => c.linkType === 'discussed_in')).toBeUndefined(); - }); - test('extracts bare slug references in text', async () => { const { candidates } = await extractPageLinks( 'docs/x', 'See companies/acme for details.', {}, 'concept', nullResolver,