From 61b812ffa92bf70849e1e8b62da2f5622e7cbda5 Mon Sep 17 00:00:00 2001 From: MoerAI Date: Thu, 21 May 2026 13:39:34 +0900 Subject: [PATCH] fix(keyword-detector): stop hyperplan firing on '.hpp' C++ header paths (fixes #4215) The hyperplan trigger \b(hyperplan|hpp)\b/i matched 'hpp' inside common C++ header references like 'check interface.hpp' or 'open buffer.hpp'. The leading '.' is a non-word character, so \b is already satisfied and the false positive fires the hyperplan-mode prompt on routine code questions. Split the alternation so 'hpp' additionally requires that the preceding character is neither a word character nor a '.'. This preserves every existing trigger ('hpp do this', '/hpp ...', mid-sentence usage, mixed case) while rejecting filename uses of the .hpp extension. The longer 'hyperplan' keyword keeps the original \b boundary semantics. Reproduction (added regression tests): - 'please help to check interface.hpp' must NOT fire - 'open src/include/audio/buffer.hpp and fix the leak' must NOT fire All 14 cases in hyperplan.test.ts pass (12 existing + 2 new), broader keyword-detector suite stays green (92 pass), typecheck clean. --- src/hooks/keyword-detector/hyperplan.test.ts | 40 +++++++++++++++++++ .../keyword-detector/hyperplan/default.ts | 7 +++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/hooks/keyword-detector/hyperplan.test.ts b/src/hooks/keyword-detector/hyperplan.test.ts index 8565bccdb..167b119b6 100644 --- a/src/hooks/keyword-detector/hyperplan.test.ts +++ b/src/hooks/keyword-detector/hyperplan.test.ts @@ -122,6 +122,46 @@ describe("keyword-detector hyperplan keyword", () => { expect(textPart!.text).not.toContain("") }) + test("should NOT trigger hyperplan when 'hpp' is the extension of a C++ header path", async () => { + // given - text references a .hpp file, which is extremely common in C++ codebases + const sessionID = "hyperplan-hpp-extension-session" + getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID) + const hook = createKeywordDetectorHook(createMockPluginInput()) + const output = { + message: {} as Record, + parts: [{ type: "text", text: "please help to check interface.hpp" }], + } + + // when - keyword detection runs on a message that only contains 'hpp' as a file extension + await hook["chat.message"]({ sessionID }, output) + + // then - hyperplan must NOT fire just because '.hpp' appears as a file extension + const textPart = output.parts.find(p => p.type === "text") + expect(textPart).toBeDefined() + expect(textPart!.text).toBe("please help to check interface.hpp") + expect(textPart!.text).not.toContain("") + }) + + test("should NOT trigger hyperplan when path-like '.hpp' appears in a deeper file path", async () => { + // given - text contains a longer path ending in .hpp (free of other trigger words like 'review') + const sessionID = "hyperplan-hpp-path-session" + getMainSessionSpy = spyOn(sessionState, "getMainSessionID").mockReturnValue(sessionID) + const hook = createKeywordDetectorHook(createMockPluginInput()) + const output = { + message: {} as Record, + parts: [{ type: "text", text: "open src/include/audio/buffer.hpp and fix the leak" }], + } + + // when - keyword detection runs + await hook["chat.message"]({ sessionID }, output) + + // then - hyperplan must not fire (the trailing '.hpp' is a header extension, not the trigger) + const textPart = output.parts.find(p => p.type === "text") + expect(textPart).toBeDefined() + expect(textPart!.text).not.toContain("") + expect(textPart!.text).not.toContain('skill(name="hyperplan")') + }) + test("should fire 'Hyperplan Mode Activated' toast when keyword detected", async () => { // given - main session and toast tracking const sessionID = "hyperplan-toast-session" diff --git a/src/hooks/keyword-detector/hyperplan/default.ts b/src/hooks/keyword-detector/hyperplan/default.ts index 1a38b75b3..cf27e087a 100644 --- a/src/hooks/keyword-detector/hyperplan/default.ts +++ b/src/hooks/keyword-detector/hyperplan/default.ts @@ -8,9 +8,14 @@ * * The detector injects a thin wrapper that loads the `hyperplan` skill, which * carries the full orchestration instructions for the 5-member adversarial team. + * + * The `hpp` shorthand uses an extra negative-lookbehind so that the very common + * C++ header-file extension `.hpp` (e.g. `interface.hpp`, `src/buffer.hpp`) + * does NOT falsely trigger hyperplan mode. A leading `.` would otherwise + * satisfy `\b` because the dot is a non-word character. See issue #4215. */ -export const HYPERPLAN_PATTERN = /\b(hyperplan|hpp)\b/i +export const HYPERPLAN_PATTERN = /\bhyperplan\b|(? **MANDATORY**: Say "HYPERPLAN MODE ENABLED!" as your first response, exactly once.