From 47d7e95b74bf4741ee68fef673b75e5831687ac0 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 23 Jul 2026 05:03:38 -0700 Subject: [PATCH] Revert "fix(frontmatter): derive validate slug from brain root, not absolute path (#2340)" This reverts commit 1a9ab6a95f340e8a66ed8baf861da233080ab53c. --- src/commands/frontmatter.ts | 29 +--------- test/frontmatter-validate-slug-565.test.ts | 65 ---------------------- 2 files changed, 2 insertions(+), 92 deletions(-) delete mode 100644 test/frontmatter-validate-slug-565.test.ts diff --git a/src/commands/frontmatter.ts b/src/commands/frontmatter.ts index 3e8e93d2a..951e35905 100644 --- a/src/commands/frontmatter.ts +++ b/src/commands/frontmatter.ts @@ -17,7 +17,7 @@ import { readFileSync, writeFileSync, existsSync, lstatSync, readdirSync } from 'fs'; import { setCliExitVerdict } from '../core/cli-force-exit.ts'; -import { join, relative, resolve, basename, dirname } from 'path'; +import { join, relative, resolve } from 'path'; import type { BrainEngine } from '../core/engine.ts'; import { loadConfig, toEngineConfig } from '../core/config.ts'; import { createEngine } from '../core/engine-factory.ts'; @@ -155,27 +155,6 @@ interface FileValidation { backupPath?: string; } -/** - * Walk up from `start` (file or dir) to the brain root — the nearest ancestor - * containing a `.git` marker — so slug derivation is brain-root-relative, - * matching how sync/extract compute slugs. Falls back to the start's own - * directory when no marker is found. Fixes #565: for a single-file target, - * `relative(resolve(target), file)` was empty (target === file) and fell back - * to the ABSOLUTE path, yielding bogus "root/brain/..." slugs and false - * SLUG_MISMATCH — which the install-hook pre-commit hook hits on every commit. - */ -function findBrainRoot(start: string): string { - const startDir = lstatSync(start).isDirectory() ? start : dirname(start); - let candidate = startDir; - for (let i = 0; i < 40; i++) { - if (existsSync(join(candidate, '.git'))) return candidate; - const parent = resolve(candidate, '..'); - if (parent === candidate) break; - candidate = parent; - } - return startDir; -} - async function runValidate(rest: string[]): Promise { const flags: ValidateFlags = { json: false, fix: false, dryRun: false }; let target: string | null = null; @@ -198,17 +177,13 @@ async function runValidate(rest: string[]): Promise { return; } - const brainRoot = findBrainRoot(resolved); const files = collectFiles(resolved); const results: FileValidation[] = []; const backupRunId = makeFrontmatterBackupRunId(); for (const file of files) { const content = readFileSync(file, 'utf8'); - const rel = relative(brainRoot, file); - // Files above/outside the brain root fall back to basename rather than - // emitting a "../"-prefixed slug for non-brain files. - const expectedSlug = slugifyPath(rel && !rel.startsWith('..') ? rel : basename(file)); + const expectedSlug = slugifyPath(relative(resolve(target), file) || file); const parsed = parseMarkdown(content, file, { validate: true, expectedSlug }); const errs = parsed.errors ?? []; const result: FileValidation = { diff --git a/test/frontmatter-validate-slug-565.test.ts b/test/frontmatter-validate-slug-565.test.ts deleted file mode 100644 index e6289146a..000000000 --- a/test/frontmatter-validate-slug-565.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, test, beforeEach, afterEach } from 'bun:test'; -import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'; -import { join } from 'path'; -import { tmpdir } from 'os'; -import { spawnSync } from 'child_process'; - -const fence = '---'; - -function runValidate(path: string): { stdout: string; code: number } { - const r = spawnSync(process.execPath, ['run', 'src/cli.ts', 'frontmatter', 'validate', path], { - encoding: 'utf8', - cwd: process.cwd(), - env: process.env, - }); - return { stdout: r.stdout ?? '', code: r.status ?? -1 }; -} - -// Regression for #565. Single-file `frontmatter validate` derived the expected -// slug from the ABSOLUTE path: `relative(resolve(target), file)` is empty when -// the target IS the file, so it fell back to `|| file` (the full path), -// yielding bogus "root/" slugs and a false SLUG_MISMATCH. The hook -// installed by `frontmatter install-hook` validates staged files one-by-one, -// so this rejected every commit in a markdown brain. The expected slug must be -// derived relative to the brain root (nearest `.git`). -describe('frontmatter validate single-file slug (#565)', () => { - let brain: string; - - beforeEach(() => { - brain = mkdtempSync(join(tmpdir(), 'fm-565-')); - mkdirSync(join(brain, '.git'), { recursive: true }); // brain-root marker - }); - - afterEach(() => { - rmSync(brain, { recursive: true, force: true }); - }); - - test('single file with a correct nested slug validates clean', () => { - mkdirSync(join(brain, 'companies'), { recursive: true }); - const f = join(brain, 'companies', 'readme.md'); - writeFileSync(f, `${fence}\ntype: company\ntitle: Readme\nslug: companies/readme\n${fence}\n\nbody`); - const { stdout, code } = runValidate(f); - expect(stdout).not.toContain('SLUG_MISMATCH'); - expect(code).toBe(0); - }); - - test('directory validation still derives brain-root-relative slugs', () => { - mkdirSync(join(brain, 'people'), { recursive: true }); - writeFileSync( - join(brain, 'people', 'alice.md'), - `${fence}\ntype: person\ntitle: Alice\nslug: people/alice\n${fence}\n\nbody`, - ); - const { stdout, code } = runValidate(join(brain, 'people')); - expect(stdout).not.toContain('SLUG_MISMATCH'); - expect(code).toBe(0); - }); - - test('file with no .git ancestor falls back to basename (no crash, no abs-path slug)', () => { - rmSync(join(brain, '.git'), { recursive: true, force: true }); - const f = join(brain, 'note.md'); - writeFileSync(f, `${fence}\ntype: note\ntitle: Note\nslug: note\n${fence}\n\nbody`); - const { stdout, code } = runValidate(f); - expect(stdout).not.toContain('SLUG_MISMATCH'); - expect(code).toBe(0); - }); -});