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
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
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"));
|
|
});
|
|
});
|
|
});
|