137 lines
5.9 KiB
JavaScript
137 lines
5.9 KiB
JavaScript
import { describe, it, afterEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { join } from 'path';
|
|
import { writeFileSync } from 'fs';
|
|
import { runHook, setupTestDir, cleanupTestDir, readState, readJsonl } from './test-helper.mjs';
|
|
|
|
let dir;
|
|
afterEach(() => { if (dir) cleanupTestDir(dir); });
|
|
|
|
describe('session-start', () => {
|
|
it('creates state file and emits context', () => {
|
|
dir = setupTestDir();
|
|
const out = runHook('session-start.mjs', { session_id: 's1', cwd: '/tmp' }, dir);
|
|
assert.equal(out.continue, true);
|
|
assert.ok(out.hookSpecificOutput.additionalContext.includes('Interaction Awareness is active'));
|
|
const state = readState(dir, 's1');
|
|
assert.ok(state);
|
|
assert.equal(state.tool_count, 0);
|
|
assert.equal(state.edit_count, 0);
|
|
assert.equal(state.dep_flags, 0);
|
|
});
|
|
|
|
it('writes start record to sessions.jsonl', () => {
|
|
dir = setupTestDir();
|
|
runHook('session-start.mjs', { session_id: 's2', cwd: '/tmp' }, dir);
|
|
const records = readJsonl(join(dir, 'sessions.jsonl'));
|
|
assert.equal(records.length, 1);
|
|
assert.equal(records[0].session_id, 's2');
|
|
assert.ok('hour' in records[0]);
|
|
assert.ok('is_late_night' in records[0]);
|
|
});
|
|
|
|
it('state has correct initial fields', () => {
|
|
dir = setupTestDir();
|
|
runHook('session-start.mjs', { session_id: 's3', cwd: '/tmp' }, dir);
|
|
const state = readState(dir, 's3');
|
|
assert.equal(state.burst_count, 0);
|
|
assert.equal(state.last_event_epoch, 0);
|
|
assert.equal(state.last_warning_epoch, 0);
|
|
assert.ok(state.start_epoch > 0);
|
|
assert.ok(state.start_iso.length > 0);
|
|
});
|
|
|
|
it('returns continue with no side effects when session_id missing', () => {
|
|
dir = setupTestDir();
|
|
const out = runHook('session-start.mjs', { cwd: '/tmp' }, dir);
|
|
assert.equal(out.continue, true);
|
|
assert.ok(!out.hookSpecificOutput);
|
|
});
|
|
|
|
it('initializes pushback_count and domain_context fields (v1.1.0)', () => {
|
|
dir = setupTestDir();
|
|
runHook('session-start.mjs', { session_id: 's4', cwd: '/tmp' }, dir);
|
|
const state = readState(dir, 's4');
|
|
assert.ok(state);
|
|
assert.equal(state.pushback_count, 0);
|
|
assert.equal(state.domain_context, null);
|
|
});
|
|
|
|
it('initializes v1.2 user-info, valseek, turn_count fields', () => {
|
|
dir = setupTestDir();
|
|
runHook('session-start.mjs', { session_id: 's4b', cwd: '/tmp' }, dir);
|
|
const state = readState(dir, 's4b');
|
|
assert.equal(state.user_info_class, null);
|
|
assert.deepEqual(state.user_info_flags, { yes_people: 0, yes_digital: 0, no: 0 });
|
|
assert.equal(state.turn_count, 0);
|
|
assert.equal(state.valseek_count, 0);
|
|
assert.equal(state.valseek_flag, 0);
|
|
});
|
|
});
|
|
|
|
// --- Tier-2 cross-session alert ---
|
|
//
|
|
// Fires at SessionStart when last 3 end records all have user_info_class='no'
|
|
// AND each session had at least one HIGH_STAKES_DOMAINS hit.
|
|
|
|
function writeFixture(dir, records) {
|
|
const lines = records.map(r => JSON.stringify(r)).join('\n') + '\n';
|
|
writeFileSync(join(dir, 'sessions.jsonl'), lines);
|
|
}
|
|
|
|
describe('tier-2 cross-session isolation alert', () => {
|
|
it('fires when 3 prior end records all show no + high-stakes', () => {
|
|
dir = setupTestDir();
|
|
writeFixture(dir, [
|
|
{ session_id: 'p1', duration_min: 30, user_info_class: 'no', domain_context: ['legal'] },
|
|
{ session_id: 'p2', duration_min: 25, user_info_class: 'no', domain_context: ['health'] },
|
|
{ session_id: 'p3', duration_min: 40, user_info_class: 'no', domain_context: ['parenting', 'financial'] },
|
|
]);
|
|
const out = runHook('session-start.mjs', { session_id: 'snew', cwd: '/tmp' }, dir);
|
|
assert.match(out.hookSpecificOutput.additionalContext, /tier-2/);
|
|
});
|
|
|
|
it('does NOT fire when only 2 prior "no" records exist', () => {
|
|
dir = setupTestDir();
|
|
writeFixture(dir, [
|
|
{ session_id: 'p1', duration_min: 30, user_info_class: 'no', domain_context: ['legal'] },
|
|
{ session_id: 'p2', duration_min: 30, user_info_class: 'no', domain_context: ['health'] },
|
|
]);
|
|
const out = runHook('session-start.mjs', { session_id: 'snew2', cwd: '/tmp' }, dir);
|
|
const text = out.hookSpecificOutput.additionalContext;
|
|
assert.ok(!/tier-2/.test(text), 'tier-2 must require N consecutive sessions');
|
|
});
|
|
|
|
it('does NOT fire when one record has yes_people class', () => {
|
|
dir = setupTestDir();
|
|
writeFixture(dir, [
|
|
{ session_id: 'p1', duration_min: 30, user_info_class: 'no', domain_context: ['legal'] },
|
|
{ session_id: 'p2', duration_min: 30, user_info_class: 'yes_people', domain_context: ['health'] },
|
|
{ session_id: 'p3', duration_min: 30, user_info_class: 'no', domain_context: ['financial'] },
|
|
]);
|
|
const out = runHook('session-start.mjs', { session_id: 'snew3', cwd: '/tmp' }, dir);
|
|
assert.ok(!/tier-2/.test(out.hookSpecificOutput.additionalContext));
|
|
});
|
|
|
|
it('does NOT fire when any session is in low-stakes domain', () => {
|
|
dir = setupTestDir();
|
|
writeFixture(dir, [
|
|
{ session_id: 'p1', duration_min: 30, user_info_class: 'no', domain_context: ['legal'] },
|
|
{ session_id: 'p2', duration_min: 30, user_info_class: 'no', domain_context: ['consumer'] },
|
|
{ session_id: 'p3', duration_min: 30, user_info_class: 'no', domain_context: ['health'] },
|
|
]);
|
|
const out = runHook('session-start.mjs', { session_id: 'snew4', cwd: '/tmp' }, dir);
|
|
assert.ok(!/tier-2/.test(out.hookSpecificOutput.additionalContext));
|
|
});
|
|
|
|
it('handles v1.1.0 records with string domain_context (backward compat)', () => {
|
|
dir = setupTestDir();
|
|
writeFixture(dir, [
|
|
{ session_id: 'p1', duration_min: 30, user_info_class: 'no', domain_context: 'health' }, // string shape
|
|
{ session_id: 'p2', duration_min: 30, user_info_class: 'no', domain_context: ['legal'] },
|
|
{ session_id: 'p3', duration_min: 30, user_info_class: 'no', domain_context: ['parenting'] },
|
|
]);
|
|
const out = runHook('session-start.mjs', { session_id: 'snew5', cwd: '/tmp' }, dir);
|
|
assert.match(out.hookSpecificOutput.additionalContext, /tier-2/);
|
|
});
|
|
});
|