feat(linkedin-studio): SB-S3e — retire dead content-history + read-side brain reconcile [skip-docs]

The LAST second-brain slice; the S0–S3e arc is now complete.

(b) Retire the dead, zero-reader content-history.md across its 8 plumbing
surfaces: the flaky Stop-hook writer prose, the template (git-rm'd), the
migrate-data.mjs B1 MOVE entry + its test assertions, .gitignore, the gate
SC2_CLASSES guard, the data-path ref-doc, and a session-start comment. SC1
grep = 0; migrate suite 5/5 (R8 idempotency demonstrated).

(c) `brain reconcile` — read-side triple-post reconciliation joining silo 1
(## Recent Posts, auto-tracked creation) to the silo 2↔3 graph, surfacing the
coverage gap: posts created via the plugin but never `brain ingest`-ed. New
pure core scripts/brain/src/reconcile.ts (parseRecentPosts tracks the WRITER
format state-updater.mjs:116, NOT the date-only pruner; reconcileRecentPosts
matches hook→record.body→graph for the in-graph/in-brain-only/orphaned tiers;
loadRecentPosts reads STATE_FILE via the canonical getStateFile() chain — a new
cross-seam read, the state file lives outside the brain dataRoot). Wired as the
`brain reconcile` CLI subcommand (inline parsePublishedRecord loader, not the
body-less listPublished). Read-only: never writes the state silo.

Tests (verified live): gate 99/0/0 (ASSERT floor 82→84; new Section 16f
self-test + CLI grep) · brain 127/127 (floor 114→127, +13 reconcile) · hook
suite 136/136. SC4/SC6 end-to-end run is a real behavioural pass (STATE_FILE
seam read + fallback). Honest limit: read-side cannot reconstruct un-captured
specifics/trends — auto-capture is the flagged follow-up.

Brief/plan: docs/second-brain/{brief,plan}-sb-s3e.md (fbad29d). Go-before-code
gate cleared (operator: retire · read-side · build-now).

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:
Kjell Tore Guttormsen 2026-06-23 22:13:33 +02:00
commit 68f6283d8a
12 changed files with 487 additions and 54 deletions

View file

@ -34,7 +34,6 @@ describe('migrateData', () => {
if (withRuntime) {
writeDeep(join(pluginRoot, 'assets/voice-samples/authentic-voice-samples.local.md'), 'REAL VOICE 227 lines');
writeDeep(join(pluginRoot, 'assets/drafts/queue.json'), '{"version":1,"queue":[]}');
writeDeep(join(pluginRoot, 'assets/analytics/content-history.md'), '# history');
writeDeep(join(pluginRoot, 'assets/analytics/exports/content-2026-W22.csv'), 'a,b\n1,2\n');
writeDeep(join(pluginRoot, 'assets/analytics/posts/2026-05-26.json'), '{"x":1}');
writeDeep(join(pluginRoot, 'assets/analytics/weekly-reports/2026-W22.json'), '{"w":22}');
@ -47,18 +46,16 @@ describe('migrateData', () => {
}
}
test('(a) gitignored runtime files MOVED: source gone, dest byte-equal, voice drops .local, content-history moved', () => {
test('(a) gitignored runtime files MOVED: source gone, dest byte-equal, voice drops .local', () => {
setup({ withRuntime: true });
migrateData({ pluginRoot });
assert.ok(!existsSync(join(pluginRoot, 'assets/voice-samples/authentic-voice-samples.local.md')), 'voice source gone');
assert.ok(!existsSync(join(pluginRoot, 'assets/drafts/queue.json')), 'queue source gone');
assert.ok(!existsSync(join(pluginRoot, 'assets/analytics/content-history.md')), 'content-history source gone');
assert.ok(!existsSync(join(pluginRoot, 'assets/analytics/exports/content-2026-W22.csv')), 'export source gone');
assert.equal(readFileSync(join(dataRoot, 'voice-samples/authentic-voice-samples.md'), 'utf-8'), 'REAL VOICE 227 lines');
assert.equal(readFileSync(join(dataRoot, 'drafts/queue.json'), 'utf-8'), '{"version":1,"queue":[]}');
assert.ok(existsSync(join(dataRoot, 'analytics/content-history.md')));
assert.ok(existsSync(join(dataRoot, 'analytics/exports/content-2026-W22.csv')));
assert.ok(existsSync(join(dataRoot, 'analytics/posts/2026-05-26.json')));
assert.ok(existsSync(join(dataRoot, 'analytics/weekly-reports/2026-W22.json')));

View file

@ -30,7 +30,6 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const MOVE_FILES = [
['assets/voice-samples/authentic-voice-samples.local.md', 'voice-samples/authentic-voice-samples.md'], // D6: drop .local
['assets/drafts/queue.json', 'drafts/queue.json'],
['assets/analytics/content-history.md', 'analytics/content-history.md'], // B1
['config/user-profile.local.md', 'profile/user-profile.md'], // D1 (expected-absent today)
];

View file

@ -91,7 +91,7 @@ let context = '';
// M0: relocate per-user data to the external root on first run. Idempotent
// (silent no-op once .migrated exists); must precede every moved-path read
// (voice/profile/scaffold scores, queue, content-history).
// (voice/profile/scaffold scores, queue).
let m0Migration = { status: 'skip', moved: [], copied: [] };
try {
m0Migration = migrateData();