feat(linkedin-studio): second-brain SB-S0 foundation — brain/ scaffold + profile fold + id/provenance spine [skip-docs]
SB-S0 (Foundation) of the second-brain arc: a new TS package scripts/brain/
(structural copy of specifics-bank) establishing the spine later slices hang on.
TDD throughout (failing test first); fold scope = P1+P2 only per operator decision.
- types.ts: one provenance vocab (human|published|ai-draft) + the six-field
ProfileFact record + two-layer ProfileDoc.
- id.ts: slugify + mintEntityId (sha256[:12], kind-namespaced, slug-keyed so the
id is stable across value edits) + normalizeProvenance (throws on unknown). [SC4]
- profile.ts: no-YAML line-grammar parse/serialize (parse∘serialize = identity over
the whole doc; values may contain ]/|/quotes) [SC2] + foldUserProfile: lossless,
idempotent, source-absent-aware fold of config/user-profile.template.md. Pinned
extraction — P1 labeled scalars (group-headers skipped, [placeholder]→empty) + P2
expertise group; stops at "### Research Tooling" so deferred explainer prose can't
leak in as fields. Checkbox-prefs (Goals/Tone/MCPs/Assets) deferred (§8). [SC3]
- dataRoot.ts: inline per-package resolver (repo idiom; no new seam → SC6).
- scaffold.ts/cli.ts: idempotent `brain init` — brain/{index,profile,operations}.md
+ journal/ + ingest/{inbox,published} under the data-dir; compare-then-skip, never
clobbers a user edit. No session-start wiring (SB-S2 owns it). [SC1]
Gate: new BRAIN floor in test-runner.sh (34 tests; trends/specifics/contract floors
unchanged) + anti-erosion floor 74→75. Full gate 90/0/0, tsc --noEmit clean. [SC5/SC6]
No new command/agent/reference → no version bump, no structure-count change.
Refs docs/second-brain/{brief,plan-sb-s0,architecture}.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
This commit is contained in:
parent
d6acb5d36c
commit
8c927198f5
17 changed files with 1554 additions and 8 deletions
113
scripts/brain/tests/fold.test.ts
Normal file
113
scripts/brain/tests/fold.test.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { foldUserProfile } from "../src/profile.js";
|
||||
import { mintEntityId } from "../src/id.js";
|
||||
import type { ProfileDoc } from "../src/types.js";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const TEMPLATE = readFileSync(join(here, "../../../config/user-profile.template.md"), "utf8");
|
||||
const POPULATED = readFileSync(join(here, "../fixtures/user-profile.populated.md"), "utf8");
|
||||
|
||||
const D1 = "2026-06-23";
|
||||
const D2 = "2026-07-01";
|
||||
|
||||
/** Look a fact up by its source label via the canonical id. */
|
||||
const byLabel = (doc: ProfileDoc, label: string) =>
|
||||
doc.static.find((f) => f.id === mintEntityId({ kind: "profile-field", key: label }));
|
||||
|
||||
const FIELD_COUNT = 27; // 22 P1 labeled scalars + 5 P2 expertise areas
|
||||
|
||||
describe("foldUserProfile (SC3)", () => {
|
||||
describe("SC3a — source-absent", () => {
|
||||
const doc = foldUserProfile({ templateText: TEMPLATE, today: D1 });
|
||||
|
||||
test("lists every template field (22 P1 + 5 P2), no dynamic facts", () => {
|
||||
assert.equal(doc.static.length, FIELD_COUNT);
|
||||
assert.equal(doc.dynamic.length, 0);
|
||||
});
|
||||
|
||||
test("placeholder fields fold to an empty value", () => {
|
||||
assert.equal(byLabel(doc, "Name")?.value, "");
|
||||
assert.equal(byLabel(doc, "Current Role")?.value, "");
|
||||
assert.equal(byLabel(doc, "expertise-area-1")?.value, "");
|
||||
assert.equal(byLabel(doc, "Follower count")?.value, "");
|
||||
assert.equal(byLabel(doc, "Topics to AVOID")?.value, "");
|
||||
});
|
||||
|
||||
test("the lone literal template field (Disclaimer) keeps its boilerplate", () => {
|
||||
assert.equal(byLabel(doc, "Important Disclaimer")?.value.startsWith("All articles"), true);
|
||||
});
|
||||
|
||||
test("deferred Research-Tooling prose does not leak in as fields", () => {
|
||||
assert.equal(byLabel(doc, "Always-available floor (no MCP needed)"), undefined);
|
||||
assert.equal(byLabel(doc, "Preferred order (optional)"), undefined);
|
||||
});
|
||||
|
||||
test("every folded fact has the canonical shape", () => {
|
||||
for (const f of doc.static) {
|
||||
assert.match(f.id, /^[0-9a-f]{12}$/);
|
||||
assert.equal(f.provenance, "human");
|
||||
assert.equal(f.status, "active");
|
||||
assert.equal(f.evidence_count, 0);
|
||||
assert.equal(f.first_seen, D1);
|
||||
assert.equal(f.last_seen, D1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("SC3b — populated (lossless)", () => {
|
||||
const doc = foldUserProfile({ templateText: TEMPLATE, instanceText: POPULATED, today: D1 });
|
||||
|
||||
test("same field-set size — fold neither drops nor invents fields", () => {
|
||||
assert.equal(doc.static.length, FIELD_COUNT);
|
||||
});
|
||||
|
||||
test("every filled value is carried (none dropped)", () => {
|
||||
assert.equal(doc.static.every((f) => f.value !== ""), true);
|
||||
});
|
||||
|
||||
test("specific filled values land on the right fields", () => {
|
||||
assert.equal(byLabel(doc, "Name")?.value, "Jordan Avery");
|
||||
assert.equal(byLabel(doc, "Current Role")?.value, "Senior Data Engineer");
|
||||
assert.equal(byLabel(doc, "expertise-area-1")?.value, "Streaming data pipelines");
|
||||
assert.equal(byLabel(doc, "expertise-area-5")?.value, "Team data literacy");
|
||||
assert.equal(byLabel(doc, "Topics to AVOID")?.value, "Crypto");
|
||||
assert.equal(byLabel(doc, "Follower count")?.value, "2,400");
|
||||
assert.equal(byLabel(doc, "Phrases you commonly use")?.value, '"ship the boring thing"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("SC3c — idempotent re-run", () => {
|
||||
test("re-running source-absent over its own output changes nothing", () => {
|
||||
const a = foldUserProfile({ templateText: TEMPLATE, today: D1 });
|
||||
const a2 = foldUserProfile({ templateText: TEMPLATE, existing: a, today: D1 });
|
||||
assert.deepEqual(a2, a);
|
||||
});
|
||||
|
||||
test("re-running populated over its own output changes nothing (no dup)", () => {
|
||||
const b = foldUserProfile({ templateText: TEMPLATE, instanceText: POPULATED, today: D1 });
|
||||
const b2 = foldUserProfile({ templateText: TEMPLATE, instanceText: POPULATED, existing: b, today: D1 });
|
||||
assert.deepEqual(b2, b);
|
||||
assert.equal(b2.static.length, FIELD_COUNT);
|
||||
});
|
||||
|
||||
test("a later run bumps last_seen but preserves first_seen and value", () => {
|
||||
const b = foldUserProfile({ templateText: TEMPLATE, instanceText: POPULATED, today: D1 });
|
||||
const later = foldUserProfile({ templateText: TEMPLATE, instanceText: POPULATED, existing: b, today: D2 });
|
||||
const name = byLabel(later, "Name");
|
||||
assert.equal(name?.first_seen, D1);
|
||||
assert.equal(name?.last_seen, D2);
|
||||
assert.equal(name?.value, "Jordan Avery");
|
||||
});
|
||||
|
||||
test("re-folding source-absent over a populated existing never wipes a filled value", () => {
|
||||
const b = foldUserProfile({ templateText: TEMPLATE, instanceText: POPULATED, today: D1 });
|
||||
const over = foldUserProfile({ templateText: TEMPLATE, existing: b, today: D2 });
|
||||
assert.equal(byLabel(over, "Name")?.value, "Jordan Avery");
|
||||
});
|
||||
});
|
||||
});
|
||||
73
scripts/brain/tests/id.test.ts
Normal file
73
scripts/brain/tests/id.test.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { slugify, mintEntityId, normalizeProvenance } from "../src/id.js";
|
||||
|
||||
describe("id module (SC4)", () => {
|
||||
describe("slugify", () => {
|
||||
test("lowercases, trims, and dashes non-alphanumeric runs", () => {
|
||||
assert.equal(slugify("Current Role"), "current-role");
|
||||
});
|
||||
|
||||
test("collapses repeated separators and strips edge dashes", () => {
|
||||
assert.equal(slugify(" Core Expertise Areas (5 topics) "), "core-expertise-areas-5-topics");
|
||||
});
|
||||
|
||||
test("is stable across case + whitespace variation of the same label", () => {
|
||||
assert.equal(slugify("Current Role"), slugify(" current ROLE "));
|
||||
});
|
||||
});
|
||||
|
||||
describe("mintEntityId", () => {
|
||||
test("is deterministic — same seed yields the same id", () => {
|
||||
assert.equal(
|
||||
mintEntityId({ kind: "profile-field", key: "Current Role" }),
|
||||
mintEntityId({ kind: "profile-field", key: "Current Role" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("is stable across label case/whitespace (id keyed on the slug)", () => {
|
||||
assert.equal(
|
||||
mintEntityId({ kind: "profile-field", key: "Current Role" }),
|
||||
mintEntityId({ kind: "profile-field", key: " current ROLE " }),
|
||||
);
|
||||
});
|
||||
|
||||
test("different key yields a different id", () => {
|
||||
assert.notEqual(
|
||||
mintEntityId({ kind: "profile-field", key: "Current Role" }),
|
||||
mintEntityId({ kind: "profile-field", key: "Organization" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("different kind yields a different id (kind is part of the seed)", () => {
|
||||
assert.notEqual(
|
||||
mintEntityId({ kind: "profile-field", key: "role" }),
|
||||
mintEntityId({ kind: "expertise-area", key: "role" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("id is 12 lowercase hex characters", () => {
|
||||
assert.match(mintEntityId({ kind: "profile-field", key: "Name" }), /^[0-9a-f]{12}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeProvenance", () => {
|
||||
test("returns each accepted value unchanged", () => {
|
||||
assert.equal(normalizeProvenance("human"), "human");
|
||||
assert.equal(normalizeProvenance("published"), "published");
|
||||
assert.equal(normalizeProvenance("ai-draft"), "ai-draft");
|
||||
});
|
||||
|
||||
test("trims and lowercases before matching", () => {
|
||||
assert.equal(normalizeProvenance(" Human "), "human");
|
||||
assert.equal(normalizeProvenance("AI-DRAFT"), "ai-draft");
|
||||
});
|
||||
|
||||
test("throws on anything outside the provenance vocabulary", () => {
|
||||
assert.throws(() => normalizeProvenance("robot"));
|
||||
assert.throws(() => normalizeProvenance(""));
|
||||
assert.throws(() => normalizeProvenance("humanoid"));
|
||||
});
|
||||
});
|
||||
});
|
||||
80
scripts/brain/tests/profile.test.ts
Normal file
80
scripts/brain/tests/profile.test.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { parseProfile, serializeProfile } from "../src/profile.js";
|
||||
import { SCHEMA_VERSION } from "../src/types.js";
|
||||
import type { ProfileDoc, ProfileFact } from "../src/types.js";
|
||||
|
||||
const fact = (over: Partial<ProfileFact> = {}): ProfileFact => ({
|
||||
id: "abc123def456",
|
||||
value: "a value",
|
||||
first_seen: "2026-06-23",
|
||||
last_seen: "2026-06-23",
|
||||
evidence_count: 0,
|
||||
provenance: "human",
|
||||
status: "active",
|
||||
...over,
|
||||
});
|
||||
|
||||
describe("profile line-grammar (SC2)", () => {
|
||||
test("serialized text exposes both section headers", () => {
|
||||
const text = serializeProfile({ schemaVersion: SCHEMA_VERSION, static: [], dynamic: [] });
|
||||
assert.match(text, /^## Static$/m);
|
||||
assert.match(text, /^## Dynamic$/m);
|
||||
assert.match(text, /^schemaVersion: 1$/m);
|
||||
});
|
||||
|
||||
test("parse ∘ serialize is identity over the whole doc", () => {
|
||||
const doc: ProfileDoc = {
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
static: [
|
||||
fact({ id: "0123456789ab", value: "Kjell Tore", provenance: "human" }),
|
||||
fact({ id: "fedcba987654", value: "", evidence_count: 3, provenance: "published" }),
|
||||
],
|
||||
dynamic: [
|
||||
fact({ id: "aaaabbbbcccc", value: "leans contrarian", status: "superseded", last_seen: "2026-06-20" }),
|
||||
],
|
||||
};
|
||||
assert.deepEqual(parseProfile(serializeProfile(doc)), doc);
|
||||
});
|
||||
|
||||
test("a value containing ], | and quotes round-trips intact", () => {
|
||||
const tricky = 'uses "scare quotes" | pipes | and a ] bracket';
|
||||
const doc: ProfileDoc = {
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
static: [fact({ value: tricky })],
|
||||
dynamic: [],
|
||||
};
|
||||
const round = parseProfile(serializeProfile(doc));
|
||||
assert.equal(round.static[0].value, tricky);
|
||||
assert.deepEqual(round, doc);
|
||||
});
|
||||
|
||||
test("an empty-value fact round-trips to an empty string (the source-absent case)", () => {
|
||||
const doc: ProfileDoc = {
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
static: [fact({ value: "" })],
|
||||
dynamic: [],
|
||||
};
|
||||
const round = parseProfile(serializeProfile(doc));
|
||||
assert.equal(round.static[0].value, "");
|
||||
assert.deepEqual(round, doc);
|
||||
});
|
||||
|
||||
test("section attribution is preserved (static vs dynamic do not bleed)", () => {
|
||||
const doc: ProfileDoc = {
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
static: [fact({ id: "111111111111", value: "static one" })],
|
||||
dynamic: [fact({ id: "222222222222", value: "dynamic one" })],
|
||||
};
|
||||
const round = parseProfile(serializeProfile(doc));
|
||||
assert.equal(round.static.length, 1);
|
||||
assert.equal(round.dynamic.length, 1);
|
||||
assert.equal(round.static[0].value, "static one");
|
||||
assert.equal(round.dynamic[0].value, "dynamic one");
|
||||
});
|
||||
|
||||
test("an unsupported schemaVersion throws", () => {
|
||||
assert.throws(() => parseProfile("# Profile\n\nschemaVersion: 99\n\n## Static\n\n## Dynamic\n"));
|
||||
});
|
||||
});
|
||||
78
scripts/brain/tests/scaffold.test.ts
Normal file
78
scripts/brain/tests/scaffold.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { describe, test, beforeEach, afterEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, rmSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
import { initBrain } from "../src/scaffold.js";
|
||||
import { parseProfile } from "../src/profile.js";
|
||||
|
||||
const EXPECTED_DIRS = ["brain", "brain/journal", "ingest", "ingest/inbox", "ingest/published"];
|
||||
const EXPECTED_FILES = ["brain/profile.md", "brain/index.md", "brain/operations.md"];
|
||||
|
||||
describe("initBrain scaffold (SC1)", () => {
|
||||
let root: string;
|
||||
const prevEnv = process.env.LINKEDIN_STUDIO_DATA;
|
||||
|
||||
beforeEach(() => {
|
||||
root = mkdtempSync(join(tmpdir(), "brain-scaffold-"));
|
||||
process.env.LINKEDIN_STUDIO_DATA = root;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (prevEnv === undefined) delete process.env.LINKEDIN_STUDIO_DATA;
|
||||
else process.env.LINKEDIN_STUDIO_DATA = prevEnv;
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("creates the full brain/ + ingest/ tree at the runtime data-path", () => {
|
||||
const res = initBrain();
|
||||
for (const d of EXPECTED_DIRS) {
|
||||
assert.ok(existsSync(join(root, d)) && statSync(join(root, d)).isDirectory(), `dir ${d}`);
|
||||
}
|
||||
for (const f of EXPECTED_FILES) {
|
||||
assert.ok(existsSync(join(root, f)), `file ${f}`);
|
||||
}
|
||||
// first run reports everything as created, nothing skipped
|
||||
assert.equal(res.skipped.length, 0);
|
||||
assert.ok(res.created.length >= EXPECTED_FILES.length);
|
||||
});
|
||||
|
||||
test("profile.md is a parseable two-layer doc seeded from the template", () => {
|
||||
initBrain();
|
||||
const doc = parseProfile(readFileSync(join(root, "brain/profile.md"), "utf8"));
|
||||
assert.equal(doc.schemaVersion, 1);
|
||||
assert.ok(doc.static.length > 0, "static layer seeded from the template field-set");
|
||||
});
|
||||
|
||||
test("index.md and operations.md carry their real seed anchors", () => {
|
||||
initBrain();
|
||||
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||
const ops = readFileSync(join(root, "brain/operations.md"), "utf8");
|
||||
for (const trib of ["voice-samples", "specifics-bank", "trends", "analytics", "ingest"]) {
|
||||
assert.match(index, new RegExp(trib), `index mentions ${trib}`);
|
||||
}
|
||||
assert.match(ops, /Who I am now/);
|
||||
assert.match(ops, /## Plans/);
|
||||
assert.match(ops, /## Ideas/);
|
||||
});
|
||||
|
||||
test("a second invocation is a no-op — every target skipped, content unchanged", () => {
|
||||
initBrain();
|
||||
const before = EXPECTED_FILES.map((f) => readFileSync(join(root, f), "utf8"));
|
||||
const res2 = initBrain();
|
||||
assert.equal(res2.created.length, 0, "nothing re-created");
|
||||
assert.ok(res2.skipped.length >= EXPECTED_FILES.length, "everything skipped");
|
||||
const after = EXPECTED_FILES.map((f) => readFileSync(join(root, f), "utf8"));
|
||||
assert.deepEqual(after, before, "no clobber of existing content");
|
||||
});
|
||||
|
||||
test("a second invocation never clobbers a user-edited file", () => {
|
||||
initBrain();
|
||||
const opsPath = join(root, "brain/operations.md");
|
||||
const edited = readFileSync(opsPath, "utf8") + "\n## Plans\n- ship SB-S1\n";
|
||||
writeFileSync(opsPath, edited, "utf8");
|
||||
initBrain();
|
||||
assert.equal(readFileSync(opsPath, "utf8"), edited, "user edit preserved");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue