test / detector.test.js
// Detector unit tests. Compiled output is checked first; this file is run with
// plain node against dist/detector.js after `pnpm exec tsc`.
const assert = require("node:assert");
const { StreamRepetitionDetector, defaultDetectorConfig } = require("../dist/detector.js");
function feed(detector, text, chunkSize = 7) {
for (let i = 0; i < text.length; i += chunkSize) {
const hit = detector.push(text.slice(i, i + chunkSize));
if (hit) return hit;
}
return null;
}
// 1. Word loop ("wait wait wait ...") triggers.
{
const d = new StreamRepetitionDetector();
const hit = feed(d, "let me think\n" + "wait ".repeat(30));
assert.ok(hit, "word loop should trigger");
assert.strictEqual(hit.kind, "consecutive-unit");
console.log("ok 1 word loop ->", hit.kind, JSON.stringify(hit.unit), hit.count);
}
// 2. Normal prose never triggers.
{
const d = new StreamRepetitionDetector();
const normal =
"The quick brown fox jumps over the lazy dog. " +
"In general, models that repeat themselves produce unreadable output. " +
"We should therefore guard the stream and stop early when a loop appears. " +
"This paragraph contains varied vocabulary and no consecutive identical units.";
assert.strictEqual(feed(d, normal), null, "normal prose must not trigger");
console.log("ok 2 normal prose safe");
}
// 3. Single-char loop triggers.
{
const d = new StreamRepetitionDetector();
const hit = feed(d, "hmm" + "。".repeat(25));
assert.ok(hit, "single-char loop should trigger");
console.log("ok 3 single-char loop ->", JSON.stringify(hit.unit), hit.count);
}
// 4. Markdown table separators do not trigger (ignoredChars).
{
const d = new StreamRepetitionDetector();
let text = "| a | b |\n";
for (let i = 0; i < 40; i++) text += "|---|---|\n";
assert.strictEqual(feed(d, text), null, "markdown table rules must not trigger");
console.log("ok 4 markdown table safe");
}
// 5. Sentence-level loop (window chant) triggers even if unit > maxUnitLength.
{
const d = new StreamRepetitionDetector({ threshold: 10, chantThreshold: 4 });
const sentence = "this sentence is longer than the maximum repeated unit length";
const hit = feed(d, sentence.repeat(8));
assert.ok(hit, "sentence loop should trigger");
console.log("ok 5 sentence loop ->", hit.kind, hit.count);
}
// 6. Code block raises the threshold (x3): 10 repeats inside code are safe.
{
const d = new StreamRepetitionDetector({ threshold: 10, codeBlockMultiplier: 3 });
const inCode = "```\n" + "-----\n".repeat(12) + "```\n";
// '-' is in ignoredChars so strip it differently: use a real code-like repeat.
const inCode2 = "```\n" + "0x00\n".repeat(12) + "```\n";
assert.strictEqual(feed(d, inCode2), null, "12 repeats in code should be safe (threshold 30)");
console.log("ok 6 code block relaxed");
}
// 7. Same repeat outside code triggers at threshold 10.
{
const d = new StreamRepetitionDetector({ threshold: 10, codeBlockMultiplier: 3 });
const hit = feed(d, "0x00\n".repeat(12));
assert.ok(hit, "12 repeats outside code should trigger (threshold 10)");
console.log("ok 7 outside code triggers ->", JSON.stringify(hit.unit), hit.count);
}
// 8. Whitespace differences are ignored ("a a a" == "aaa" pattern).
{
const d = new StreamRepetitionDetector({ threshold: 5 });
const hit = feed(d, "foo\nbar baz\n".repeat(1) + "na na\n".repeat(10));
assert.ok(hit, "spaced word loop should trigger");
console.log("ok 8 spaced loop ->", JSON.stringify(hit.unit), hit.count);
}
// 9. Reset clears state.
{
const d = new StreamRepetitionDetector({ threshold: 5 });
feed(d, "loop ".repeat(4));
d.reset();
assert.strictEqual(feed(d, "fresh text here"), null, "after reset nothing should linger");
console.log("ok 9 reset works");
}
console.log("\nAll detector tests passed.");
test / detector.test.js
// Detector unit tests. Compiled output is checked first; this file is run with
// plain node against dist/detector.js after `pnpm exec tsc`.
const assert = require("node:assert");
const { StreamRepetitionDetector, defaultDetectorConfig } = require("../dist/detector.js");
function feed(detector, text, chunkSize = 7) {
for (let i = 0; i < text.length; i += chunkSize) {
const hit = detector.push(text.slice(i, i + chunkSize));
if (hit) return hit;
}
return null;
}
// 1. Word loop ("wait wait wait ...") triggers.
{
const d = new StreamRepetitionDetector();
const hit = feed(d, "let me think\n" + "wait ".repeat(30));
assert.ok(hit, "word loop should trigger");
assert.strictEqual(hit.kind, "consecutive-unit");
console.log("ok 1 word loop ->", hit.kind, JSON.stringify(hit.unit), hit.count);
}
// 2. Normal prose never triggers.
{
const d = new StreamRepetitionDetector();
const normal =
"The quick brown fox jumps over the lazy dog. " +
"In general, models that repeat themselves produce unreadable output. " +
"We should therefore guard the stream and stop early when a loop appears. " +
"This paragraph contains varied vocabulary and no consecutive identical units.";
assert.strictEqual(feed(d, normal), null, "normal prose must not trigger");
console.log("ok 2 normal prose safe");
}
// 3. Single-char loop triggers.
{
const d = new StreamRepetitionDetector();
const hit = feed(d, "hmm" + "。".repeat(25));
assert.ok(hit, "single-char loop should trigger");
console.log("ok 3 single-char loop ->", JSON.stringify(hit.unit), hit.count);
}
// 4. Markdown table separators do not trigger (ignoredChars).
{
const d = new StreamRepetitionDetector();
let text = "| a | b |\n";
for (let i = 0; i < 40; i++) text += "|---|---|\n";
assert.strictEqual(feed(d, text), null, "markdown table rules must not trigger");
console.log("ok 4 markdown table safe");
}
// 5. Sentence-level loop (window chant) triggers even if unit > maxUnitLength.
{
const d = new StreamRepetitionDetector({ threshold: 10, chantThreshold: 4 });
const sentence = "this sentence is longer than the maximum repeated unit length";
const hit = feed(d, sentence.repeat(8));
assert.ok(hit, "sentence loop should trigger");
console.log("ok 5 sentence loop ->", hit.kind, hit.count);
}
// 6. Code block raises the threshold (x3): 10 repeats inside code are safe.
{
const d = new StreamRepetitionDetector({ threshold: 10, codeBlockMultiplier: 3 });
const inCode = "```\n" + "-----\n".repeat(12) + "```\n";
// '-' is in ignoredChars so strip it differently: use a real code-like repeat.
const inCode2 = "```\n" + "0x00\n".repeat(12) + "```\n";
assert.strictEqual(feed(d, inCode2), null, "12 repeats in code should be safe (threshold 30)");
console.log("ok 6 code block relaxed");
}
// 7. Same repeat outside code triggers at threshold 10.
{
const d = new StreamRepetitionDetector({ threshold: 10, codeBlockMultiplier: 3 });
const hit = feed(d, "0x00\n".repeat(12));
assert.ok(hit, "12 repeats outside code should trigger (threshold 10)");
console.log("ok 7 outside code triggers ->", JSON.stringify(hit.unit), hit.count);
}
// 8. Whitespace differences are ignored ("a a a" == "aaa" pattern).
{
const d = new StreamRepetitionDetector({ threshold: 5 });
const hit = feed(d, "foo\nbar baz\n".repeat(1) + "na na\n".repeat(10));
assert.ok(hit, "spaced word loop should trigger");
console.log("ok 8 spaced loop ->", JSON.stringify(hit.unit), hit.count);
}
// 9. Reset clears state.
{
const d = new StreamRepetitionDetector({ threshold: 5 });
feed(d, "loop ".repeat(4));
d.reset();
assert.strictEqual(feed(d, "fresh text here"), null, "after reset nothing should linger");
console.log("ok 9 reset works");
}
console.log("\nAll detector tests passed.");