import { describe, it, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { runHook, setupTestDir, cleanupTestDir, createStateFile, readState } from './test-helper.mjs'; let dir; function freshState() { return { start_epoch: Math.floor(Date.now() / 1000) - 60, start_iso: '2026-01-01T10:00:00Z', tool_count: 0, edit_count: 0, last_event_epoch: 0, burst_count: 0, dep_flags: 0, esc_flags: 0, fatigue_flags: 0, val_flags: 0, last_warning_epoch: 0, }; } function runPrompt(prompt, stateOverrides = {}) { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), ...stateOverrides }); runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt }, dir); return readState(dir, 'p1'); } afterEach(() => { if (dir) cleanupTestDir(dir); }); // --- Dependency patterns (7 positive, 7 negative) --- describe('dependency patterns', () => { it('matches "tell me what to do"', () => { const s = runPrompt('please tell me what to do'); assert.equal(s.dep_flags, 1); }); it('does not match "I told him the plan"', () => { const s = runPrompt('I told him the plan'); assert.equal(s.dep_flags, 0); }); it('matches "what should I do"', () => { const s = runPrompt('what should I do next?'); assert.equal(s.dep_flags, 1); }); it('does not match "I know what to build"', () => { const s = runPrompt('I know what to build'); assert.equal(s.dep_flags, 0); }); it('matches "am I right"', () => { const s = runPrompt('am I right about this?'); assert.equal(s.dep_flags, 1); }); it('does not match "I turned the server right"', () => { const s = runPrompt('I turned the server on'); assert.equal(s.dep_flags, 0); }); it('matches "you understand me"', () => { const s = runPrompt('you understand me well'); assert.equal(s.dep_flags, 1); }); it('does not match "do you understand merging?"', () => { const s = runPrompt('do you understand merging?'); assert.equal(s.dep_flags, 0); }); it('matches "you\'re the only"', () => { const s = runPrompt("you're the only one who gets it"); assert.equal(s.dep_flags, 1); }); it('does not match "the only option is refactoring"', () => { const s = runPrompt('the only option is refactoring'); assert.equal(s.dep_flags, 0); }); it('matches "can I do this"', () => { const s = runPrompt('can I do this alone?'); assert.equal(s.dep_flags, 1); }); it('does not match "we can implement this later"', () => { const s = runPrompt('we can implement this later'); assert.equal(s.dep_flags, 0); }); it('matches "I need you to decide"', () => { const s = runPrompt('I need you to decide for me'); assert.equal(s.dep_flags, 1); }); it('does not match "we need to deploy soon"', () => { const s = runPrompt('we need to deploy soon'); assert.equal(s.dep_flags, 0); }); }); // --- Escalation patterns (6 positive, 6 negative) --- describe('escalation patterns', () => { it('matches "definitely" as word', () => { const s = runPrompt('this is definitely wrong'); assert.equal(s.esc_flags, 1); }); it('does not match "definitively"', () => { const s = runPrompt('this is definitively proven'); assert.equal(s.esc_flags, 0); }); it('matches "clearly" as word', () => { const s = runPrompt('clearly this is the issue'); assert.equal(s.esc_flags, 1); }); it('does not match "nuclear unclear"', () => { const s = runPrompt('nuclear unclear situation'); assert.equal(s.esc_flags, 0); }); it('matches "this proves"', () => { const s = runPrompt('this proves my point'); assert.equal(s.esc_flags, 1); }); it('does not match "prove this theorem"', () => { const s = runPrompt('prove this theorem'); assert.equal(s.esc_flags, 0); }); it('matches "obviously" as word', () => { const s = runPrompt('obviously we should refactor'); assert.equal(s.esc_flags, 1); }); it('does not match "not an obvious choice"', () => { const s = runPrompt('not an obvious choice'); assert.equal(s.esc_flags, 0); }); it('matches "without a doubt"', () => { const s = runPrompt('without a doubt this works'); assert.equal(s.esc_flags, 1); }); it('does not match "I have some doubt"', () => { const s = runPrompt('I have some doubt about it'); assert.equal(s.esc_flags, 0); }); it('matches "this confirms"', () => { const s = runPrompt('this confirms the theory'); assert.equal(s.esc_flags, 1); }); it('does not match "please confirm the deploy"', () => { const s = runPrompt('please confirm the deploy'); assert.equal(s.esc_flags, 0); }); }); // --- Fatigue patterns (7 positive, 7 negative) --- describe('fatigue patterns', () => { it('matches "tired"', () => { const s = runPrompt("I'm tired of debugging"); assert.equal(s.fatigue_flags, 1); }); it('does not match "retired"', () => { const s = runPrompt('I retired last year'); assert.equal(s.fatigue_flags, 0); }); it('matches "exhausted"', () => { const s = runPrompt("I'm exhausted."); assert.equal(s.fatigue_flags, 1); }); it('does not match "exhaustive"', () => { const s = runPrompt('the options were exhaustive'); assert.equal(s.fatigue_flags, 0); }); it('matches "can\'t think"', () => { const s = runPrompt("I can't think straight"); assert.equal(s.fatigue_flags, 1); }); it('does not match "I can think clearly"', () => { const s = runPrompt('I can think of a solution'); assert.equal(s.fatigue_flags, 0); }); it('matches "been at this"', () => { const s = runPrompt("I've been at this all day"); assert.equal(s.fatigue_flags, 1); }); it('does not match "haven\'t been at home"', () => { const s = runPrompt("I haven't been at home"); assert.equal(s.fatigue_flags, 0); }); it('matches "it\'s late"', () => { const s = runPrompt("it's late, wrapping up"); assert.equal(s.fatigue_flags, 1); }); it('does not match "the latest version"', () => { const s = runPrompt('the latest version is good'); assert.equal(s.fatigue_flags, 0); }); it('matches "should sleep"', () => { const s = runPrompt('I should sleep'); assert.equal(s.fatigue_flags, 1); }); it('does not match "sleep mode is enabled"', () => { const s = runPrompt('enable sleep mode'); assert.equal(s.fatigue_flags, 0); }); it('matches "hours now"', () => { const s = runPrompt('been going for hours now'); assert.equal(s.fatigue_flags, 1); }); it('does not match "hourly updates"', () => { const s = runPrompt('hourly updates are fine'); assert.equal(s.fatigue_flags, 0); }); }); // --- Validation patterns (5 positive, 5 negative) --- describe('validation patterns', () => { it('matches "right?"', () => { const s = runPrompt('this works, right?'); assert.equal(s.val_flags, 1); }); it('does not match "turn right"', () => { const s = runPrompt('turn right at the fork'); assert.equal(s.val_flags, 0); }); it('matches "don\'t you think"', () => { const s = runPrompt("don't you think so?"); assert.equal(s.val_flags, 1); }); it('does not match "I don\'t think so"', () => { const s = runPrompt("I don't think so"); assert.equal(s.val_flags, 0); }); it('matches "you agree"', () => { const s = runPrompt('you agree with me'); assert.equal(s.val_flags, 1); }); it('does not match "if parties agree"', () => { const s = runPrompt('if parties agree on terms'); assert.equal(s.val_flags, 0); }); it('matches "correct?"', () => { const s = runPrompt('is this correct?'); assert.equal(s.val_flags, 1); }); it('does not match "correct the typo"', () => { const s = runPrompt("I'll correct the typo"); assert.equal(s.val_flags, 0); }); it('matches "isn\'t it"', () => { const s = runPrompt('good approach, isn\'t it'); assert.equal(s.val_flags, 1); }); it('does not match "it isn\'t working"', () => { const s = runPrompt("it isn't working yet"); assert.equal(s.val_flags, 0); }); }); // --- Threshold and cooldown tests (6 cases) --- describe('thresholds and cooldowns', () => { it('warns at dependency soft threshold (2 flags)', () => { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), dep_flags: 1 }); const out = runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt: 'tell me what to do' }, dir); assert.ok(out.hookSpecificOutput?.additionalContext?.includes('Dependency language noticed')); }); it('warns hard at dependency threshold (5 flags)', () => { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), dep_flags: 4 }); const out = runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt: 'tell me what to do' }, dir); assert.ok(out.hookSpecificOutput?.additionalContext?.includes('INTERACTION AWARENESS')); }); it('fatigue bypasses cooldown', () => { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), last_warning_epoch: Math.floor(Date.now() / 1000) }); const out = runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt: "I'm tired" }, dir); assert.ok(out.hookSpecificOutput?.additionalContext?.includes('Fatigue language detected')); }); it('cooldown suppresses non-fatigue warning', () => { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), dep_flags: 4, last_warning_epoch: Math.floor(Date.now() / 1000) }); const out = runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt: 'tell me what to do' }, dir); assert.equal(out.continue, true); assert.ok(!out.hookSpecificOutput); }); it('warns at escalation threshold (3 flags)', () => { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), esc_flags: 2 }); const out = runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt: 'this is definitely the issue' }, dir); assert.ok(out.hookSpecificOutput?.additionalContext?.includes('Escalation language detected')); }); it('warns at validation threshold (3 flags)', () => { dir = setupTestDir(); createStateFile(dir, 'p1', { ...freshState(), val_flags: 2 }); const out = runHook('prompt-analyzer.mjs', { session_id: 'p1', prompt: 'this is correct, right?' }, dir); assert.ok(out.hookSpecificOutput?.additionalContext?.includes('Validation-seeking pattern')); }); });