mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 17:02:19 +00:00
Compare commits
2
Commits
master
...
reland/2340
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b6606c7bb | ||
|
|
c18cee8c87 |
@@ -17,7 +17,7 @@
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, lstatSync, readdirSync } from 'fs';
|
||||
import { setCliExitVerdict } from '../core/cli-force-exit.ts';
|
||||
import { join, relative, resolve } from 'path';
|
||||
import { join, relative, resolve, basename, dirname } from 'path';
|
||||
import type { BrainEngine } from '../core/engine.ts';
|
||||
import { loadConfig, toEngineConfig } from '../core/config.ts';
|
||||
import { createEngine } from '../core/engine-factory.ts';
|
||||
@@ -155,6 +155,27 @@ 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<void> {
|
||||
const flags: ValidateFlags = { json: false, fix: false, dryRun: false };
|
||||
let target: string | null = null;
|
||||
@@ -177,13 +198,17 @@ async function runValidate(rest: string[]): Promise<void> {
|
||||
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 expectedSlug = slugifyPath(relative(resolve(target), file) || file);
|
||||
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 parsed = parseMarkdown(content, file, { validate: true, expectedSlug });
|
||||
const errs = parsed.errors ?? [];
|
||||
const result: FileValidation = {
|
||||
|
||||
@@ -13,10 +13,25 @@
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
||||
import { configureGateway, resetGateway } from '../src/core/ai/gateway.ts';
|
||||
|
||||
let engine: PGLiteEngine;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Pin the embedding dim to 1536 BEFORE initSchema. vec() hardcodes
|
||||
// Float32Array(1536), but initSchema sizes vector columns from
|
||||
// process-global gateway state (getEmbeddingDimensions(), default 1280).
|
||||
// Whether this file passes therefore depends on which test files run
|
||||
// before it in the shard; adding test files to the repo reshuffles the
|
||||
// weight-packed shards, so unrelated PRs trip it ("expected 1280
|
||||
// dimensions, not 1536"). Same fix + rationale as
|
||||
// doctor-hidden-by-search-policy.test.ts (#2801),
|
||||
// engine-find-trajectory.test.ts and cosine-rescore-column.test.ts.
|
||||
configureGateway({
|
||||
embedding_model: 'openai:text-embedding-3-large',
|
||||
embedding_dimensions: 1536,
|
||||
env: { OPENAI_API_KEY: 'sk-test-facts-engine' },
|
||||
});
|
||||
engine = new PGLiteEngine();
|
||||
await engine.connect({});
|
||||
await engine.initSchema();
|
||||
@@ -24,6 +39,7 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await engine.disconnect();
|
||||
resetGateway();
|
||||
});
|
||||
|
||||
const vec = (...vals: number[]): Float32Array => {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
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/<abs-path>" 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user