linkedin-studio/scripts/brain/tests/scaffold.test.ts
Kjell Tore Guttormsen 8c927198f5 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
2026-06-23 14:05:24 +02:00

78 lines
3.2 KiB
TypeScript

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");
});
});