Meta-awareness tools for healthy AI interaction patterns. Detects reinforcement loops, scope escalation, and compulsive patterns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
313 lines
10 KiB
JavaScript
313 lines
10 KiB
JavaScript
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'));
|
|
});
|
|
});
|