diff --git a/packages/coding-agent/test/modes/components/user-message-keywords.test.ts b/packages/coding-agent/test/modes/components/user-message-keywords.test.ts
new file mode 100644
index 000000000..4a527595b
--- /dev/null
+++ b/packages/coding-agent/test/modes/components/user-message-keywords.test.ts
@@ -0,0 +1,36 @@
+import { beforeAll, describe, expect, it } from "bun:test";
+import { UserMessageComponent } from "../../../src/modes/components/user-message";
+import { initTheme } from "../../../src/modes/theme/theme";
+
+beforeAll(async () => {
+ await initTheme(false);
+});
+
+function render(text: string): string {
+ return new UserMessageComponent(text).render(80).join("\n");
+}
+
+describe("UserMessageComponent magic-keyword highlighting", () => {
+ it("gradient-paints a magic keyword in the rendered (sent) message bubble", () => {
+ const raw = render("please orchestrate the rollout");
+ // Visible text is preserved.
+ expect(Bun.stripANSI(raw)).toContain("please orchestrate the rollout");
+ // The keyword is gradient-painted: a per-character foreground sequence is emitted,
+ // and the word no longer survives as a contiguous run in the rendered bytes.
+ expect(raw).toContain("\x1b[38");
+ expect(raw).not.toContain("orchestrate");
+ });
+
+ it("does not paint a keyword inside an inline code span", () => {
+ const raw = render("ship the `orchestrate` helper");
+ expect(Bun.stripANSI(raw)).toContain("orchestrate");
+ // Code spans render through the code style as a single run — the word stays intact.
+ expect(raw).toContain("orchestrate");
+ });
+
+ it("does not paint a keyword inside a fenced code block", () => {
+ const raw = render("intro\n```\norchestrate\n```");
+ expect(Bun.stripANSI(raw)).toContain("orchestrate");
+ expect(raw).toContain("orchestrate");
+ });
+});
diff --git a/packages/coding-agent/test/modes/magic-keywords.test.ts b/packages/coding-agent/test/modes/magic-keywords.test.ts
new file mode 100644
index 000000000..8abd4977f
--- /dev/null
+++ b/packages/coding-agent/test/modes/magic-keywords.test.ts
@@ -0,0 +1,46 @@
+import { beforeAll, describe, expect, it } from "bun:test";
+import { highlightMagicKeywords } from "../../src/modes/magic-keywords";
+import { initTheme } from "../../src/modes/theme/theme";
+
+beforeAll(async () => {
+ // Gradient palettes read the active theme's color mode.
+ await initTheme(false);
+});
+
+describe("highlightMagicKeywords", () => {
+ it("paints every magic keyword in a single prose pass, preserving visible text", () => {
+ const input = "first ultrathink then orchestrate the workflow";
+ const decorated = highlightMagicKeywords(input);
+ expect(decorated).not.toBe(input);
+ expect(decorated).toContain("\x1b[38");
+ expect(Bun.stripANSI(decorated)).toBe(input);
+ // Each keyword is gradient-painted character-by-character, so none survives as a
+ // contiguous run in the decorated output.
+ for (const keyword of ["ultrathink", "orchestrate", "workflow"]) {
+ expect(decorated).not.toContain(keyword);
+ expect(Bun.stripANSI(decorated)).toContain(keyword);
+ }
+ });
+
+ it("never paints keywords inside code spans, fenced blocks, or XML sections", () => {
+ const input = "`ultrathink`\n```\norchestrate\n```\nworkflow";
+ expect(highlightMagicKeywords(input)).toBe(input);
+ });
+
+ it("paints only the prose occurrence when the keyword also appears in code", () => {
+ const decorated = highlightMagicKeywords("`orchestrate` but please orchestrate now");
+ // The code-span occurrence stays literal; the prose one is split by gradient escapes.
+ expect(decorated).toContain("`orchestrate`");
+ expect(Bun.stripANSI(decorated)).toBe("`orchestrate` but please orchestrate now");
+ // Exactly one prose occurrence painted ⇒ one contiguous "orchestrate" remains (the code one).
+ expect(decorated.split("orchestrate").length - 1).toBe(1);
+ });
+
+ it("restores the supplied foreground after each painted keyword", () => {
+ const reset = "\x1b[38;2;1;2;3m";
+ const decorated = highlightMagicKeywords("go orchestrate go", reset);
+ expect(decorated).toContain(reset);
+ // The reset must land before the trailing prose so it keeps the bubble color.
+ expect(decorated.endsWith(`${reset} go`)).toBe(true);
+ });
+});
diff --git a/packages/coding-agent/test/modes/markdown-prose.test.ts b/packages/coding-agent/test/modes/markdown-prose.test.ts
new file mode 100644
index 000000000..e0b4104d9
--- /dev/null
+++ b/packages/coding-agent/test/modes/markdown-prose.test.ts
@@ -0,0 +1,80 @@
+import { describe, expect, it } from "bun:test";
+import { keywordInProse, maskNonProse } from "../../src/modes/markdown-prose";
+
+const ORCHESTRATE = /\borchestrate\b/i;
+
+describe("maskNonProse", () => {
+ it("preserves length and leaves plain prose untouched", () => {
+ const text = "please orchestrate the rollout across teams";
+ expect(maskNonProse(text)).toBe(text);
+ });
+
+ it("returns the input unchanged when no code/markup constructs are present", () => {
+ // Fast path: no backtick, angle bracket, or tilde fence.
+ const text = "a multi line\nmessage with no markup";
+ expect(maskNonProse(text)).toBe(text);
+ });
+
+ it("blanks fenced code blocks while keeping indices aligned", () => {
+ const text = "before\n```\norchestrate\n```\nafter orchestrate";
+ const masked = maskNonProse(text);
+ expect(masked.length).toBe(text.length);
+ // The fenced occurrence is blanked; the prose occurrence survives at its index.
+ expect(masked.indexOf("orchestrate")).toBe(text.lastIndexOf("orchestrate"));
+ expect(masked.startsWith("before\n")).toBe(true);
+ });
+
+ it("blanks tilde fences and language-tagged fences", () => {
+ expect(maskNonProse("~~~\norchestrate\n~~~").includes("orchestrate")).toBe(false);
+ expect(maskNonProse("```ts\nconst orchestrate = 1\n```").includes("orchestrate")).toBe(false);
+ });
+
+ it("treats an unterminated fence as code through end of text", () => {
+ expect(maskNonProse("```\norchestrate").includes("orchestrate")).toBe(false);
+ });
+});
+
+describe("keywordInProse", () => {
+ it("matches a standalone keyword in prose", () => {
+ expect(keywordInProse("please orchestrate this", ORCHESTRATE)).toBe(true);
+ expect(keywordInProse("orchestrate", ORCHESTRATE)).toBe(true);
+ });
+
+ it("ignores keywords inside inline code spans", () => {
+ expect(keywordInProse("use `orchestrate` now", ORCHESTRATE)).toBe(false);
+ expect(keywordInProse("``a `orchestrate` b``", ORCHESTRATE)).toBe(false);
+ });
+
+ it("treats an unmatched backtick run as literal text", () => {
+ // A lone backtick opens no span, so the keyword is still prose.
+ expect(keywordInProse("the ` then orchestrate", ORCHESTRATE)).toBe(true);
+ });
+
+ it("ignores keywords inside fenced code blocks", () => {
+ expect(keywordInProse("```\norchestrate\n```", ORCHESTRATE)).toBe(false);
+ expect(keywordInProse("text\n```\norchestrate here\n```\ndone", ORCHESTRATE)).toBe(false);
+ });
+
+ it("ignores keywords inside XML/HTML sections, including nested and comments", () => {
+ expect(keywordInProse("orchestrate", ORCHESTRATE)).toBe(false);
+ expect(keywordInProse("orchestrate", ORCHESTRATE)).toBe(false);
+ expect(keywordInProse("", ORCHESTRATE)).toBe(false);
+ expect(keywordInProse('orchestrate', ORCHESTRATE)).toBe(false);
+ });
+
+ it("still matches prose outside of code/markup regions", () => {
+ expect(keywordInProse("orchestrate then orchestrate", ORCHESTRATE)).toBe(true);
+ expect(keywordInProse("run `x` then orchestrate", ORCHESTRATE)).toBe(true);
+ expect(keywordInProse("```\ncode\n```\norchestrate", ORCHESTRATE)).toBe(true);
+ });
+
+ it("does not over-mask on a stray less-than or an unbalanced tag", () => {
+ expect(keywordInProse("a < b and orchestrate", ORCHESTRATE)).toBe(true);
+ //
has no matching close, so only the tag is masked, not the rest.
+ expect(keywordInProse("
orchestrate after", ORCHESTRATE)).toBe(true);
+ });
+
+ it("respects word boundaries regardless of region", () => {
+ expect(keywordInProse("reorchestrate the orchestration", ORCHESTRATE)).toBe(false);
+ });
+});
diff --git a/packages/coding-agent/test/modes/orchestrate.test.ts b/packages/coding-agent/test/modes/orchestrate.test.ts
index 7f6da09ff..f17b513ba 100644
--- a/packages/coding-agent/test/modes/orchestrate.test.ts
+++ b/packages/coding-agent/test/modes/orchestrate.test.ts
@@ -10,22 +10,35 @@ beforeAll(() => {
});
describe("orchestrate keyword detection", () => {
- it("matches the standalone word in any case", () => {
+ it("matches the lowercase word delimited by whitespace or a string edge", () => {
expect(containsOrchestrate("orchestrate")).toBe(true);
- expect(containsOrchestrate("Orchestrate")).toBe(true);
- expect(containsOrchestrate("ORCHESTRATE")).toBe(true);
expect(containsOrchestrate("please orchestrate this rollout")).toBe(true);
- expect(containsOrchestrate("do it. orchestrate.")).toBe(true);
+ expect(containsOrchestrate("orchestrate the rollout")).toBe(true);
+ // A newline is whitespace, and end-of-string is a valid right boundary.
+ expect(containsOrchestrate("do it now\norchestrate")).toBe(true);
});
- it("ignores inflected forms and embedded substrings", () => {
+ it("ignores casing, inflections, punctuation-adjacent, and path-embedded forms", () => {
+ expect(containsOrchestrate("Orchestrate")).toBe(false);
+ expect(containsOrchestrate("ORCHESTRATE")).toBe(false);
expect(containsOrchestrate("orchestrated the build")).toBe(false);
expect(containsOrchestrate("orchestrating now")).toBe(false);
expect(containsOrchestrate("a clean orchestration")).toBe(false);
expect(containsOrchestrate("it orchestrates well")).toBe(false);
expect(containsOrchestrate("reorchestrate everything")).toBe(false);
+ // The reported bug: a path/extension is not whitespace, so the word never triggers.
+ expect(containsOrchestrate("packages/coding-agent/src/modes/orchestrate.ts")).toBe(false);
+ expect(containsOrchestrate("do it. orchestrate.")).toBe(false);
expect(containsOrchestrate("nothing to see here")).toBe(false);
});
+
+ it("ignores keywords inside code spans, fenced blocks, and XML sections", () => {
+ expect(containsOrchestrate("use `orchestrate` here")).toBe(false);
+ expect(containsOrchestrate("```\norchestrate\n```")).toBe(false);
+ expect(containsOrchestrate("orchestrate")).toBe(false);
+ // A real prose request alongside code still triggers.
+ expect(containsOrchestrate("run `setup` then orchestrate the rollout")).toBe(true);
+ });
});
describe("orchestrate keyword highlighting", () => {
@@ -38,8 +51,12 @@ describe("orchestrate keyword highlighting", () => {
it("leaves text without the standalone keyword untouched", () => {
expect(highlightOrchestrate("nothing here")).toBe("nothing here");
- // Probe hits the substring but the word boundary fails — no decoration.
+ // Probe hits the substring but the whitespace boundary fails — no decoration.
expect(highlightOrchestrate("orchestrated builds")).toBe("orchestrated builds");
+ expect(highlightOrchestrate("Orchestrate this")).toBe("Orchestrate this");
+ // The reported bug: a filename must not be painted.
+ const filePath = "packages/coding-agent/src/modes/orchestrate.ts";
+ expect(highlightOrchestrate(filePath)).toBe(filePath);
});
it("does not cross-trigger with the ultrathink highlighter", () => {
diff --git a/packages/coding-agent/test/modes/workflow.test.ts b/packages/coding-agent/test/modes/workflow.test.ts
index 658b5a797..bb010bc6b 100644
--- a/packages/coding-agent/test/modes/workflow.test.ts
+++ b/packages/coding-agent/test/modes/workflow.test.ts
@@ -8,18 +8,21 @@ beforeAll(() => {
});
describe("workflow keyword detection", () => {
- it("matches the standalone word (singular or plural) in any case", () => {
+ it("matches the lowercase word (singular or plural) delimited by whitespace", () => {
expect(containsWorkflow("workflow")).toBe(true);
- expect(containsWorkflow("Workflow")).toBe(true);
- expect(containsWorkflow("WORKFLOW")).toBe(true);
expect(containsWorkflow("please workflow this rollout")).toBe(true);
- expect(containsWorkflow("do it. workflow.")).toBe(true);
expect(containsWorkflow("run these workflows")).toBe(true);
+ expect(containsWorkflow("design the workflow")).toBe(true);
});
- it("ignores inflected forms and embedded substrings", () => {
+ it("ignores casing, inflections, punctuation-adjacent, and path-embedded forms", () => {
+ expect(containsWorkflow("Workflow")).toBe(false);
+ expect(containsWorkflow("WORKFLOW")).toBe(false);
expect(containsWorkflow("workflowed the build")).toBe(false);
expect(containsWorkflow("reworkflow everything")).toBe(false);
+ // A path/extension is not whitespace, so the word never triggers.
+ expect(containsWorkflow("packages/coding-agent/test/modes/workflow.test.ts")).toBe(false);
+ expect(containsWorkflow("do it. workflow.")).toBe(false);
expect(containsWorkflow("nothing to see here")).toBe(false);
});
});
@@ -34,8 +37,11 @@ describe("workflow keyword highlighting", () => {
});
it("leaves text without the standalone keyword untouched", () => {
- // Probe hits the substring but the word boundary fails — no decoration.
+ // Probe hits the substring but the whitespace boundary fails — no decoration.
expect(highlightWorkflow("workflowed builds")).toBe("workflowed builds");
+ expect(highlightWorkflow("Workflow this")).toBe("Workflow this");
+ const filePath = "packages/coding-agent/test/modes/workflow.test.ts";
+ expect(highlightWorkflow(filePath)).toBe(filePath);
});
});