import { describe, expect, it } from "bun:test";
import { computeUserMessageMetrics, EMPTY_USER_METRICS } from "../src/user-metrics";
describe("computeUserMessageMetrics", () => {
it("returns zeros for empty / whitespace-only text", () => {
expect(computeUserMessageMetrics("")).toEqual({ ...EMPTY_USER_METRICS });
expect(computeUserMessageMetrics(" \n\t ")).toEqual({ ...EMPTY_USER_METRICS });
});
it("counts a sentence as yelling when >50% of its letters are uppercase", () => {
const m = computeUserMessageMetrics("STOP DOING THAT NOW");
expect(m.yelling).toBe(1);
});
it("treats mostly-lowercase sentences as not yelling even with embedded CAPS", () => {
const m = computeUserMessageMetrics("Hi there, please STOP doing THAT immediately, it is really annoying.");
expect(m.yelling).toBe(0);
});
it("ignores very short uppercase fragments below the letter floor", () => {
expect(computeUserMessageMetrics("OK").yelling).toBe(0);
expect(computeUserMessageMetrics("WIP.").yelling).toBe(0);
});
it("counts multiple yelling sentences separated by terminators", () => {
const m = computeUserMessageMetrics("WHY IS THIS BROKEN? FIX IT NOW!! please.");
expect(m.yelling).toBe(2);
});
it("does not flag camelCase / acronyms inside otherwise-lowercase prose", () => {
const m = computeUserMessageMetrics("call getHTMLParser then exit");
expect(m.yelling).toBe(0);
});
it("matches profanity case-insensitively at word boundaries only", () => {
const m = computeUserMessageMetrics("oh FUCK this is bullshit, damn it");
expect(m.profanity).toBe(3);
expect(computeUserMessageMetrics("import classes from module").profanity).toBe(0);
});
it("counts quality-dismissal vocabulary as profanity", () => {
const m = computeUserMessageMetrics("this is garbage, useless and horrible work");
expect(m.profanity).toBe(3);
});
it("folds drama runs / elongated interjections / dot trails into `anguish`", () => {
const m = computeUserMessageMetrics("why!!! seriously??? omg!?!?!?");
expect(m.anguish).toBeGreaterThanOrEqual(3);
expect(computeUserMessageMetrics("ok!! sure??").anguish).toBe(0);
});
it("absorbs shift-key `1` mishits into a single drama burst", () => {
expect(computeUserMessageMetrics("what!!!111").anguish).toBeGreaterThanOrEqual(1);
expect(computeUserMessageMetrics("are you serious!?!?!??111").anguish).toBeGreaterThanOrEqual(1);
expect(computeUserMessageMetrics("port 8111 please").anguish).toBe(0);
});
describe("negation signal", () => {
it("fires on line-leading correction openers", () => {
expect(computeUserMessageMetrics("no this is the renderer").negation).toBe(1);
expect(computeUserMessageMetrics("nope, still wrong").negation).toBe(1);
expect(computeUserMessageMetrics("nah look at this").negation).toBe(1);
expect(computeUserMessageMetrics("wrong file").negation).toBe(1);
expect(computeUserMessageMetrics("nvm got it").negation).toBe(1);
});
it("does not fire on words that share a prefix with negation tokens", () => {
expect(computeUserMessageMetrics("now everything works").negation).toBe(0);
expect(computeUserMessageMetrics("nobody knows why").negation).toBe(0);
expect(computeUserMessageMetrics("normal operation resumed").negation).toBe(0);
});
it("only anchors at the start of the message - mid-message `no`/`No` lines do not fire", () => {
expect(computeUserMessageMetrics("i instantly get Finalizing ->\nNo speech detected").negation).toBe(0);
expect(computeUserMessageMetrics("Authentication failed\n\nWrong user name or password").negation).toBe(0);
});
it("strips `[Image #N]` placeholders so message-leading negation still fires", () => {
expect(computeUserMessageMetrics("[Image #1] nope still broken").negation).toBe(1);
});
it("fires on explicit rejection phrases", () => {
expect(computeUserMessageMetrics("thats not what i wanted").negation).toBe(1);
expect(computeUserMessageMetrics("that's not right").negation).toBe(1);
expect(computeUserMessageMetrics("this is not what i meant at all").negation).toBe(1);
});
});
describe("repetition signal", () => {
it("counts explicit recall verbs", () => {
expect(computeUserMessageMetrics("i meant the other file").repetition).toBe(1);
expect(computeUserMessageMetrics("i told you to skip it").repetition).toBe(1);
expect(computeUserMessageMetrics("i asked you for json not yaml").repetition).toBe(1);
expect(computeUserMessageMetrics("like i said earlier").repetition).toBe(1);
});
it("requires `you` after `i asked` to suppress neutral third-party usage", () => {
expect(computeUserMessageMetrics("i asked the committee to review").repetition).toBe(0);
expect(computeUserMessageMetrics("so i asked a bunch of experts").repetition).toBe(0);
expect(computeUserMessageMetrics("you're not doing AST rewriting like i asked").repetition).toBe(1);
});
it("counts `still` only when paired with a negative / sameness marker", () => {
expect(computeUserMessageMetrics("the agent still works fine").repetition).toBe(0);
expect(computeUserMessageMetrics("it still doesnt work").repetition).toBe(1);
expect(computeUserMessageMetrics("still the same issue").repetition).toBe(1);
expect(computeUserMessageMetrics("still failing on darwin").repetition).toBe(1);
});
});
describe("blame signal", () => {
it("fires on accusatory second-person verbs", () => {
expect(computeUserMessageMetrics("you broke the layout").blame).toBe(1);
expect(computeUserMessageMetrics("you didnt update AGENTS").blame).toBe(1);
expect(computeUserMessageMetrics("you missed a callsite").blame).toBe(1);
expect(computeUserMessageMetrics("you forgot to commit").blame).toBe(1);
expect(computeUserMessageMetrics("you keep doing that").blame).toBe(1);
});
it("does not fire on bare `you`", () => {
expect(computeUserMessageMetrics("can you fix the bug?").blame).toBe(0);
expect(computeUserMessageMetrics("could you also add a test").blame).toBe(0);
});
it("only fires on `stop X-ing` at sentence start", () => {
expect(computeUserMessageMetrics("stop touching git").blame).toBe(1);
expect(computeUserMessageMetrics("please stop making yolo changes").blame).toBe(0);
expect(computeUserMessageMetrics("ok. stop reverting things").blame).toBe(1);
expect(computeUserMessageMetrics("the loop keeps stopping").blame).toBe(0);
});
});
it("zeros out behavior signals on long structured prompts", () => {
const long = [
"no this is wrong, you broke it, i meant the other one.",
"please undo and try again.",
"acceptance: green tests.",
"thanks!",
].join("\n");
const m = computeUserMessageMetrics(long);
expect(m.negation).toBe(0);
expect(m.repetition).toBe(0);
expect(m.blame).toBe(0);
expect(m.anguish).toBe(0);
expect(m.profanity).toBe(0);
expect(m.yelling).toBe(0);
expect(m.chars).toBeGreaterThan(0);
expect(m.words).toBeGreaterThan(0);
});
it("captures multiple frustration signals on a single short message", () => {
const m = computeUserMessageMetrics("no, you broke it AGAIN. i told you it still doesnt work");
expect(m.negation).toBe(1);
expect(m.blame).toBe(1);
expect(m.repetition).toBeGreaterThanOrEqual(2);
});
});