mirror of
https://github.com/hanzoai/brain.git
synced 2026-10-30 18:46:38 +00:00
@hanzo/persona already owns what a persona IS — the JSON Schema and the
PERSONA.md frontmatter convention. This pack does not restate it: it loads the
profile, checks it against that schema, and files it in the store.
Filing it in the store is the point. brain.db is read from TS, Python, Rust and
Go, so a persona written once is the same persona in all of them. The
alternative is each runtime loading its own copy of a file and drifting, which
is the duplication the brain exists to end.
Stored twice, deliberately, because they answer different questions: as a PAGE
(the document, for search and for a human) and as FACTS, one row per trait, so
recall("persona/<id>") answers "who am I" without parsing markdown. Nested
traits become dotted predicates; an array is joined rather than exploded,
because a persona's tags is one answer and not five rows.
A half-written profile still loads — a missing required field is NAMED in a
warning, never thrown, since a persona you are still writing should not stop a
brain from opening. No PERSONA.md at all is the ordinary case and returns null.
Soft contracts throughout, like every other pack here.
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { check, facts, split } from "./index";
|
|
|
|
describe("split", () => {
|
|
it("separates frontmatter from body", () => {
|
|
const { front, body } = split("---\nname: Ada\n---\nI build things.\n");
|
|
expect(front.trim()).toBe("name: Ada");
|
|
expect(body.trim()).toBe("I build things.");
|
|
});
|
|
|
|
// A persona written as prose is a persona, not an error.
|
|
it("treats a body with no frontmatter as all body", () => {
|
|
const { front, body } = split("I build things.\n");
|
|
expect(front).toBe("");
|
|
expect(body.trim()).toBe("I build things.");
|
|
});
|
|
|
|
// An unterminated block would otherwise swallow the whole document.
|
|
it("does not eat the document when the block never closes", () => {
|
|
const { front, body } = split("---\nname: Ada\nI build things.\n");
|
|
expect(front).toBe("");
|
|
expect(body).toContain("I build things.");
|
|
});
|
|
});
|
|
|
|
describe("check", () => {
|
|
it("names only what the schema requires and the profile lacks", () => {
|
|
const schema = { required: ["id", "name"] };
|
|
expect(check({ id: "ada" }, schema)).toEqual(["name"]);
|
|
expect(check({ id: "ada", name: "Ada" }, schema)).toEqual([]);
|
|
});
|
|
|
|
it("is silent when the schema requires nothing", () => {
|
|
expect(check({}, {})).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("facts", () => {
|
|
const rows = facts(
|
|
{ id: "ada", ocean: { openness: 0.9 }, tags: ["math", "engines"] },
|
|
"persona/ada",
|
|
"PERSONA.md",
|
|
);
|
|
const by = (p: string) => rows.find((r) => r.predicate === p);
|
|
|
|
it("dots a nested trait so one trait is recallable on its own", () => {
|
|
expect(by("ocean.openness")?.object).toBe("0.9");
|
|
});
|
|
|
|
// A persona's tags are one answer, not five rows.
|
|
it("joins an array rather than exploding it", () => {
|
|
expect(by("tags")?.object).toBe("math, engines");
|
|
});
|
|
|
|
it("files every row under the one subject", () => {
|
|
expect(rows.every((r) => r.subject === "persona/ada")).toBe(true);
|
|
});
|
|
});
|