import { describe, test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdtempSync, rmSync, mkdirSync, writeFileSync, copyFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; // SB-S2: SessionStart emits a "run brain consolidate" nudge driven by the count of // published gold records + the consolidation-state.json last_run sidecar (read via // getDataRoot — the SAME root the brain CLI --apply writes to). Zero-dep, no // profile.md parse. Subprocess harness with isolated HOME + LINKEDIN_STUDIO_DATA, // mirroring session-start-trends-staleness.test.mjs. const here = dirname(fileURLToPath(import.meta.url)); const hookPath = join(here, '..', 'session-start.mjs'); const PLUGIN_ROOT = join(here, '..', '..', '..'); const STATE_TEMPLATE = join(PLUGIN_ROOT, 'config', 'state-file.template.md'); const NUDGE = 'brain consolidate'; const isoDaysAgo = (n) => new Date(Date.now() - n * 86400000).toISOString().slice(0, 10); const record = (id) => `id: ${id}\nprovenance: published\npublished_date: 2026-01-01\ncaptured_at: 2026-01-01\nsource: manual\n---\nbody`; function runHook({ withState = true, published = [], lastRun = undefined } = {}) { const home = mkdtempSync(join(tmpdir(), 'lis-bc-home-')); const data = mkdtempSync(join(tmpdir(), 'lis-bc-data-')); try { if (withState) { mkdirSync(join(home, '.claude'), { recursive: true }); copyFileSync(STATE_TEMPLATE, join(home, '.claude', 'linkedin-studio.local.md')); } if (published.length) { mkdirSync(join(data, 'ingest', 'published'), { recursive: true }); for (const id of published) writeFileSync(join(data, 'ingest', 'published', id + '.md'), record(id)); } if (lastRun !== undefined) { mkdirSync(join(data, 'brain'), { recursive: true }); writeFileSync(join(data, 'brain', 'consolidation-state.json'), JSON.stringify({ last_run: lastRun })); } const stdout = execFileSync('node', [hookPath], { input: '', env: { ...process.env, HOME: home, USERPROFILE: home, LINKEDIN_STUDIO_DATA: data }, encoding: 'utf-8', }); return JSON.parse(stdout).hookSpecificOutput.additionalContext; } finally { rmSync(home, { recursive: true, force: true }); rmSync(data, { recursive: true, force: true }); } } describe('session-start — brain consolidation-due nudge (SB-S2)', () => { test('fires when published records exist and consolidation never ran', () => { const ctx = runHook({ published: ['aaaaaaaaaaaa', 'bbbbbbbbbbbb'] }); assert.ok(ctx.includes(NUDGE), 'nudge expected when published exist + never consolidated'); assert.ok(ctx.includes('2 published'), 'reports the published count'); }); test('fires when last_run is stale (>=7d)', () => { const ctx = runHook({ published: ['aaaaaaaaaaaa'], lastRun: isoDaysAgo(10) }); assert.ok(ctx.includes(NUDGE), 'nudge expected for a 10-day-old last_run'); }); test('silent when last_run is fresh (<7d)', () => { const ctx = runHook({ published: ['aaaaaaaaaaaa'], lastRun: isoDaysAgo(2) }); assert.ok(!ctx.includes(NUDGE), 'fresh consolidation => no nudge'); }); test('silent when there are no published records (never nags)', () => { const ctx = runHook({ published: [] }); assert.ok(!ctx.includes(NUDGE), 'no published => no nudge'); }); test('fresh install (no state file) emits valid JSON and nudges brain init', () => { const ctx = runHook({ withState: false }); assert.equal(typeof ctx, 'string', 'valid JSON additionalContext on fresh install'); assert.ok(ctx.includes('Brain not initialised'), 'brain-init nudge survives the fresh-install branch'); }); });