Files
gbrain/test/filing-rules-resolution.serial.test.ts
testandDov Stern 45bd04ff9f sources harden: resolve _brain-filing-rules.json per repo instead of always using the bundled copy (#4018)
Wave-assembled from PR #4018 by @dovstern. Conflict resolution: import-block
union in src/core/brain-repo-durability.ts (master's gbrain-home/execution-env
imports kept, PR's loadFilingRules import + resolveFilingRules threading
re-applied onto master's shape). Rider: test renamed to
filing-rules-resolution.serial.test.ts (mutates HOME).

Co-Authored-By: Dov Stern <dovstern@users.noreply.github.com>
2026-08-13 12:18:13 -07:00

92 lines
3.9 KiB
TypeScript

/**
* Per-repo filing-rules resolution (#4015): `sources harden` must render a
* repo's OWN `skills/_brain-filing-rules.json` taxonomy into the managed
* AGENTS/RESOLVER block, not the bundled default — falling back to the
* bundled default only when the repo has no rules file, or that file is
* malformed. Fixture mirrors brain-repo-durability.serial.test.ts (real git
* against a local bare remote; HOME + GBRAIN_HOME redirected to a tmp dir).
*/
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { mkdtempSync, rmSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { execFileSync } from 'child_process';
import { hardenBrainRepo } from '../src/core/brain-repo-durability.ts';
const PAT = 'ghp_TESTSECRETTOKEN0123456789abcdef';
function git(cwd: string, ...args: string[]): string {
return execFileSync('git', ['-C', cwd, '-c', 'protocol.file.allow=always', ...args], {
stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8',
}).trim();
}
let root: string;
let work: string;
let bare: string;
let oldHome: string | undefined;
let oldGbrainHome: string | undefined;
function makePair(): void {
bare = mkdtempSync(join(root, 'origin-')) + '.git';
execFileSync('git', ['init', '-q', '--bare', '-b', 'main', bare], { stdio: 'ignore' });
work = mkdtempSync(join(root, 'work-'));
execFileSync('git', ['-c', 'protocol.file.allow=always', 'clone', '-q', bare, work], { stdio: 'ignore' });
git(work, 'config', 'user.email', 't@t.t');
git(work, 'config', 'user.name', 'tester');
writeFileSync(join(work, 'README.md'), 'init\n');
git(work, 'add', 'README.md'); git(work, 'commit', '-qm', 'init'); git(work, 'push', '-q', 'origin', 'main');
try { git(work, 'remote', 'set-head', 'origin', 'main'); } catch { /* */ }
}
async function harden(extra: Record<string, unknown> = {}) {
return hardenBrainRepo({ repoPath: work, sourceId: 'wiki', pat: PAT, installCron: false, ...extra });
}
function writeRepoRules(json: string): void {
mkdirSync(join(work, 'skills'), { recursive: true });
writeFileSync(join(work, 'skills', '_brain-filing-rules.json'), json);
git(work, 'add', 'skills/_brain-filing-rules.json'); git(work, 'commit', '-qm', 'filing rules');
}
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), 'frr-'));
oldHome = process.env.HOME; oldGbrainHome = process.env.GBRAIN_HOME;
process.env.HOME = mkdtempSync(join(root, 'home-'));
process.env.GBRAIN_HOME = join(process.env.HOME, '.gbrain');
process.env.GBRAIN_GIT_ALLOW_FILE_TRANSPORT = '1';
makePair();
});
afterEach(() => {
if (oldHome === undefined) delete process.env.HOME; else process.env.HOME = oldHome;
if (oldGbrainHome === undefined) delete process.env.GBRAIN_HOME; else process.env.GBRAIN_HOME = oldGbrainHome;
delete process.env.GBRAIN_GIT_ALLOW_FILE_TRANSPORT;
rmSync(root, { recursive: true, force: true });
});
describe('per-source filing-rules resolution', () => {
test('repo with its own skills/_brain-filing-rules.json → managed block carries that taxonomy', async () => {
writeRepoRules(JSON.stringify({
version: '1.0.0',
rules: [{ kind: 'widget', directory: 'widgets/', description: 'A widget page.' }],
}));
await harden();
const agents = readFileSync(join(work, 'AGENTS.md'), 'utf-8');
expect(agents).toContain('`widgets/`');
expect(agents).not.toContain('`people/`');
});
test('repo without a rules file → bundled default taxonomy (regression pin)', async () => {
await harden();
const agents = readFileSync(join(work, 'AGENTS.md'), 'utf-8');
expect(agents).toContain('`people/`');
});
test('malformed JSON in the repo file → falls back to bundled default, no throw', async () => {
writeRepoRules('{ this is not valid json');
await harden(); // must not throw
const agents = readFileSync(join(work, 'AGENTS.md'), 'utf-8');
expect(agents).toContain('`people/`');
});
});