Make brain/operations.md a READ tributary — the "operations centre" half of the
second brain. strategy-advisor now reads the dated "who I am now" anchor and
honours the frozen-past-self guard: when a profile.md fact predates or
contradicts the anchor, the anchor deprecates it (advisory/reader-side; NO
consolidate-engine change). Anti-sycophancy is INVERTED vs the profile —
profile facts stay evidence-to-TEST, the user-declared anchor is honoured.
- scaffold.ts operationsSeed(): dated-anchor convention (_As of YYYY-MM-DD:_) +
Plans/Ideas guidance; existence-skip idempotency preserved (no-clobber green).
- strategy-advisor.md: operations.md in Step 0 context-load + a consumption
contract (anchor authoritative, inversion, plans-vs-ideas, graceful absence).
- test-runner.sh Section 16e: 2 unconditional checks (non-vacuity self-test +
wiring grep, sibling-file decoy); ASSERT_BASELINE_FLOOR 80->82; BRAIN floor 113->114.
- architecture.md:80: build-row reconciled (S3d done; S3e = dead content-history
retirement + triple-post reconciliation = the new LAST S3 slice).
Gate 97/0/0; brain 114/114. TDD: RED proven (16e Check B + the scaffold
dated-anchor test both failed pre-fix) before GREEN. Splits the old S3d charter;
the hygiene half is deferred to SB-S3e. Light-Voyage hardened (scope-guardian
ALIGNED, brief-reviewer + plan-critic folds in docs 0061bf2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
/**
|
|
* `initBrain` — the idempotent runtime scaffold for the second brain (SB-S0 D1/D5).
|
|
*
|
|
* Creates the `brain/` + `ingest/` tree under the per-user data dir (via the
|
|
* `dataRoot` resolver, so `LINKEDIN_STUDIO_DATA` relocates it for tests + real
|
|
* installs). Idempotent + migration-safe: a directory that exists is left alone,
|
|
* a file that exists is NEVER rewritten (compare-then-skip, never blind-write) —
|
|
* so a second run is a no-op and a user-edited file is preserved.
|
|
*
|
|
* NO session-start wiring (SB-S2 owns that) — this only ships the invokable init.
|
|
*/
|
|
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { dataRoot } from "./dataRoot.js";
|
|
import { foldUserProfile, serializeProfile } from "./profile.js";
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
/** The shipped template that defines the canonical profile field-set. */
|
|
const TEMPLATE_PATH = join(HERE, "..", "..", "..", "config", "user-profile.template.md");
|
|
/** The runtime instance (if the user copied + filled the template). */
|
|
const INSTANCE_SUB = join("profile", "user-profile.md");
|
|
|
|
const SCAFFOLD_DIRS = ["brain/journal", "ingest/inbox", "ingest/published"];
|
|
|
|
function today(): string {
|
|
return new Date().toISOString().slice(0, 10);
|
|
}
|
|
|
|
function indexSeed(): string {
|
|
return `# Brain — Index (MOC)
|
|
|
|
> Map of Content — one screen pointing at every tributary, with a freshness flag.
|
|
> Generated by \`brain init\`; safe to hand-edit (a re-run never clobbers it).
|
|
|
|
| Tributary | What it holds | Freshness |
|
|
|-----------|---------------|-----------|
|
|
| voice-samples | writing style | — |
|
|
| specifics-bank | lived raw material | — |
|
|
| trends | external signal | — |
|
|
| analytics | performance | — |
|
|
| ingest | inbox + published (the gold signal) | — |
|
|
|
|
- [profile.md](profile.md) — semantic, two-layer (static + dynamic) profile
|
|
- [operations.md](operations.md) — plans · ideas · the "who I am now" anchor
|
|
- journal/ — episodic, append-only session log (the consolidation loop's source)
|
|
`;
|
|
}
|
|
|
|
function operationsSeed(): string {
|
|
return `# Operations
|
|
|
|
> The operations centre — where you're headed now, what you're working on, what you might
|
|
> do next. User-authored: the brain motor never writes here (a re-run of init never
|
|
> re-touches an existing file, so your edits are safe).
|
|
|
|
## Who I am now (anchor)
|
|
|
|
<!-- Frozen-past-self guard: a DATED, user-authored "where I'm headed now" statement.
|
|
It deprecates older inferences — when a profile.md fact predates or contradicts this
|
|
anchor, the anchor wins. Re-date it when your direction shifts. -->
|
|
|
|
_As of YYYY-MM-DD:_ <one or two sentences on your current direction — replace this line>
|
|
|
|
## Plans
|
|
|
|
<!-- Active commitments — what you're working toward now. One per line. -->
|
|
|
|
## Ideas
|
|
|
|
<!-- Parking lot — possible content/strategy ideas, not yet commitments. One per line. -->
|
|
`;
|
|
}
|
|
|
|
function profileSeed(): string {
|
|
const templateText = readFileSync(TEMPLATE_PATH, "utf8");
|
|
const instancePath = dataRoot(INSTANCE_SUB);
|
|
const instanceText = existsSync(instancePath) ? readFileSync(instancePath, "utf8") : undefined;
|
|
return serializeProfile(foldUserProfile({ templateText, instanceText, today: today() }));
|
|
}
|
|
|
|
export interface InitResult {
|
|
/** Data-relative paths created this run. */
|
|
created: string[];
|
|
/** Data-relative paths that already existed and were left untouched. */
|
|
skipped: string[];
|
|
}
|
|
|
|
/**
|
|
* Create the brain scaffold idempotently. Returns the created/skipped split.
|
|
* File content seeds are computed lazily, so an already-present file costs no
|
|
* template read or fold.
|
|
*/
|
|
export function initBrain(): InitResult {
|
|
const created: string[] = [];
|
|
const skipped: string[] = [];
|
|
|
|
for (const sub of SCAFFOLD_DIRS) {
|
|
const abs = dataRoot(sub);
|
|
if (existsSync(abs)) {
|
|
skipped.push(sub);
|
|
} else {
|
|
mkdirSync(abs, { recursive: true });
|
|
created.push(sub);
|
|
}
|
|
}
|
|
|
|
const files: Array<{ sub: string; seed: () => string }> = [
|
|
{ sub: "brain/profile.md", seed: profileSeed },
|
|
{ sub: "brain/index.md", seed: indexSeed },
|
|
{ sub: "brain/operations.md", seed: operationsSeed },
|
|
];
|
|
|
|
for (const { sub, seed } of files) {
|
|
const abs = dataRoot(sub);
|
|
if (existsSync(abs)) {
|
|
skipped.push(sub);
|
|
continue;
|
|
}
|
|
mkdirSync(dirname(abs), { recursive: true });
|
|
writeFileSync(abs, seed(), "utf8");
|
|
created.push(sub);
|
|
}
|
|
|
|
return { created, skipped };
|
|
}
|