// tests/kb-update/test-session-start-status.test.mjs // Verifies that hooks/scripts/session-start-context.mjs surfaces the // KB-update status file correctly per Status File Schema (plan.md L122-153). // // Same fixture statuses as test-weekly-kb-cron-flags.test.mjs so producer/ // consumer divergence is caught at test time. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir, platform as osPlatform } from 'node:os'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const HOOK = join(__dirname, '..', '..', 'hooks', 'scripts', 'session-start-context.mjs'); const PLUGIN_ROOT = join(__dirname, '..', '..'); function mkSandbox() { return mkdtempSync(join(tmpdir(), 'sshook-test-')); } function cacheDirFor(home) { if (osPlatform() === 'darwin') { return join(home, 'Library', 'Caches', 'ms-ai-architect'); } if (osPlatform() === 'win32') { return join(home, 'AppData', 'Local', 'ms-ai-architect', 'Cache'); } return join(home, '.cache', 'ms-ai-architect'); } function writeStatus(home, status) { const dir = cacheDirFor(home); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, 'kb-update-status.json'), JSON.stringify(status, null, 2)); } function runHook(home, extraEnv = {}) { return spawnSync('node', [HOOK], { env: { PATH: process.env.PATH, HOME: home, CLAUDE_PLUGIN_ROOT: PLUGIN_ROOT, ...extraEnv, }, encoding: 'utf8', timeout: 10_000, cwd: home, // not in plugin dir, so utredning/onboarding checks stay quiet }); } const baseStatus = { schema_version: 1, last_run_status: 'success', last_run_ts: '2026-05-05T10:00:00Z', duration_seconds: 412, auth_mode: 'api-key', log_file: '/tmp/kb-update.log', files_planned: 18, files_committed: 18, session_id: 'sess_demo', total_cost_usd: 1.42, tokens_input: 54000, tokens_output: 27000, max_turns_hit: false, diagnostic: null, }; test('failure status surfaces "KB-update: failure" line', () => { const home = mkSandbox(); try { writeStatus(home, { ...baseStatus, last_run_status: 'failure', diagnostic: 'No commits produced' }); const result = runHook(home); assert.equal(result.status, 0, `stderr: ${result.stderr}`); assert.match(result.stdout, /KB-update: failure/); assert.match(result.stdout, /2026-05-05T10:00:00Z/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('partial status surfaces "KB-update: partial" line', () => { const home = mkSandbox(); try { writeStatus(home, { ...baseStatus, last_run_status: 'partial', files_committed: 7, max_turns_hit: true }); const result = runHook(home); assert.equal(result.status, 0); assert.match(result.stdout, /KB-update: partial/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('budget_exceeded status surfaces a line', () => { const home = mkSandbox(); try { writeStatus(home, { ...baseStatus, last_run_status: 'budget_exceeded', files_committed: 0 }); const result = runHook(home); assert.equal(result.status, 0); assert.match(result.stdout, /KB-update: budget_exceeded/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('success status does NOT surface a KB-update line', () => { const home = mkSandbox(); try { writeStatus(home, { ...baseStatus, last_run_status: 'success' }); const result = runHook(home); assert.equal(result.status, 0); assert.doesNotMatch(result.stdout, /KB-update:/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('dry-run status does NOT surface a KB-update line', () => { const home = mkSandbox(); try { writeStatus(home, { ...baseStatus, last_run_status: 'dry-run' }); const result = runHook(home); assert.equal(result.status, 0); assert.doesNotMatch(result.stdout, /KB-update:/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('missing status file → hook still exits 0 with no KB-update line', () => { const home = mkSandbox(); try { // No status file written. const result = runHook(home); assert.equal(result.status, 0, `stderr: ${result.stderr}`); assert.doesNotMatch(result.stdout, /KB-update:/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('malformed status file → hook tolerates and exits 0', () => { const home = mkSandbox(); try { const dir = cacheDirFor(home); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, 'kb-update-status.json'), '{ this is: not, valid json'); const result = runHook(home); assert.equal(result.status, 0); assert.doesNotMatch(result.stdout, /KB-update:/); } finally { rmSync(home, { recursive: true, force: true }); } }); test('hook completes in < 1 second on warm filesystem', () => { const home = mkSandbox(); try { writeStatus(home, { ...baseStatus, last_run_status: 'failure' }); // Warm-up. runHook(home); const start = Date.now(); const result = runHook(home); const elapsed = Date.now() - start; assert.equal(result.status, 0); assert.ok(elapsed < 1000, `hook took ${elapsed}ms (>1s)`); } finally { rmSync(home, { recursive: true, force: true }); } });