import { describe, it, expect } from "vitest"; import { mkdtempSync, writeFileSync, mkdirSync } from "fs"; import { join } from "path"; import { tmpdir } from "os"; import { buildRootPairBlock, hasRootPairMarker, buildPreInitHint, computeInjectionBudget, modeAllowsPreInitHint, modeAllowsInjection, modeRequiresProtocolPath, hasProtocolPath, buildAdvisoryReminder, discoverContextWindow, estimateTokens, findAllArtifactPairs, buildInjectionPayload, outgoingMessagesHaveMarker, providerPayloadHasMarker, shouldReinjectForEvent, isRelevantTurnForReinjection, detectEditIntent, detectArchitectureSensitiveReasoning, isSensitiveAction, messagesHaveBypass, extractBypassReason, evaluateStrictBypass, buildStrictBypassGuard, ROOT_PAIR_START_MARKER, ROOT_PAIR_END_MARKER, TRUST_BOUNDARY_TEXT, BYPASS_MARKER_PREFIX, } from "../src/prompt-injection.js"; describe("prompt-injection helpers", () => { describe("canonical markers", () => { it("builds a root-pair block with markers", () => { const block = buildRootPairBlock("index content", "map content"); expect(block).toContain(ROOT_PAIR_START_MARKER); expect(block).toContain(ROOT_PAIR_END_MARKER); expect(block).toContain(TRUST_BOUNDARY_TEXT); expect(block).toContain("index content"); expect(block).toContain("map content"); }); it("detects root-pair marker in content", () => { const block = buildRootPairBlock("i", "m"); expect(hasRootPairMarker(block)).toBe(true); expect(hasRootPairMarker("plain text")).toBe(false); }); }); describe("pre-init hint", () => { it("returns a visible hint with no synthetic artifacts", () => { const hint = buildPreInitHint(); expect(hint).toContain("project_map_init"); expect(hint).toContain("📋"); expect(hint).not.toContain(ROOT_PAIR_START_MARKER); expect(hint).not.toContain("### Root index"); expect(hint).not.toContain("### Root map"); }); }); describe("budget calculation", () => { it("uses relative percent when smaller than absolute cap", () => { const budget = computeInjectionBudget( { contextBudgetPercent: 15, contextBudgetMaxTokens: 100_000 }, 200_000, ); expect(budget).toBe(30_000); }); it("uses absolute cap when smaller than relative percent", () => { const budget = computeInjectionBudget( { contextBudgetPercent: 15, contextBudgetMaxTokens: 100_000 }, 10_000_000, ); expect(budget).toBe(100_000); }); it("falls back to absolute cap when context window is unknown", () => { const budget = computeInjectionBudget( { contextBudgetPercent: 15, contextBudgetMaxTokens: 100_000 }, undefined, ); expect(budget).toBe(100_000); }); it("falls back to absolute cap when context window is zero", () => { const budget = computeInjectionBudget( { contextBudgetPercent: 15, contextBudgetMaxTokens: 100_000 }, 0, ); expect(budget).toBe(100_000); }); }); describe("mode helpers", () => { it("allows pre-init hint for all modes except off", () => { expect(modeAllowsPreInitHint("off")).toBe(false); expect(modeAllowsPreInitHint("advisory")).toBe(true); expect(modeAllowsPreInitHint("strong")).toBe(true); expect(modeAllowsPreInitHint("strict")).toBe(true); }); it("allows injection only for strong and strict", () => { expect(modeAllowsInjection("off")).toBe(false); expect(modeAllowsInjection("advisory")).toBe(false); expect(modeAllowsInjection("strong")).toBe(true); expect(modeAllowsInjection("strict")).toBe(true); }); it("requires protocol path only for strict", () => { expect(modeRequiresProtocolPath("off")).toBe(false); expect(modeRequiresProtocolPath("advisory")).toBe(false); expect(modeRequiresProtocolPath("strong")).toBe(false); expect(modeRequiresProtocolPath("strict")).toBe(true); }); }); describe("context-window discovery", () => { it("reads contextWindow from model metadata", () => { const ctx = { model: { contextWindow: 256_000 } }; expect(discoverContextWindow(ctx)).toBe(256_000); }); it("reads maxContextTokens from model metadata", () => { const ctx = { model: { maxContextTokens: 128_000 } }; expect(discoverContextWindow(ctx)).toBe(128_000); }); it("falls back to known model lookup by id", () => { expect(discoverContextWindow({ model: { id: "gpt-4o-mini" } })).toBe( 128_000, ); expect(discoverContextWindow({ model: { id: "kimi-for-coding" } })).toBe( 200_000, ); }); it("returns undefined for unknown models", () => { expect( discoverContextWindow({ model: { id: "unknown-model" } }), ).toBeUndefined(); }); it("returns undefined when model is missing", () => { expect(discoverContextWindow({})).toBeUndefined(); }); }); describe("token estimation", () => { it("estimates tokens from character count", () => { expect(estimateTokens("")).toBe(0); expect(estimateTokens("abcd")).toBe(1); expect(estimateTokens("abcde")).toBe(2); }); }); describe("artifact-pair discovery", () => { it("finds paired artifacts shallow-first", () => { const dir = mkdtempSync(join(tmpdir(), "pi-pair-test-")); mkdirSync(join(dir, "src", "nested"), { recursive: true }); writeFileSync(join(dir, ".pi-map.md"), "# root"); writeFileSync(join(dir, ".pi-map.index.md"), "# root index"); writeFileSync(join(dir, "src", ".pi-map.md"), "# src"); writeFileSync(join(dir, "src", ".pi-map.index.md"), "# src index"); writeFileSync(join(dir, "src", "nested", ".pi-map.md"), "# nested"); writeFileSync( join(dir, "src", "nested", ".pi-map.index.md"), "# nested index", ); const pairs = findAllArtifactPairs(dir); expect(pairs.map((p) => p.dir)).toEqual([".", "src", "src/nested"]); }); it("ignores directories missing one of the paired artifacts", () => { const dir = mkdtempSync(join(tmpdir(), "pi-pair-test-")); writeFileSync(join(dir, ".pi-map.md"), "# root"); // missing .pi-map.index.md intentionally expect(findAllArtifactPairs(dir)).toHaveLength(0); }); }); describe("outgoing message marker scan", () => { it("detects marker in string content", () => { const messages = [{ content: `hello ${ROOT_PAIR_START_MARKER} world` }]; expect(outgoingMessagesHaveMarker(messages)).toBe(true); }); it("detects marker in text field", () => { const messages = [{ text: `before ${ROOT_PAIR_START_MARKER} after` }]; expect(outgoingMessagesHaveMarker(messages)).toBe(true); }); it("detects marker in array content blocks", () => { const messages = [ { content: [ { type: "text", text: "prefix" }, { type: "text", text: ROOT_PAIR_START_MARKER }, ], }, ]; expect(outgoingMessagesHaveMarker(messages)).toBe(true); }); it("returns false when no marker is present", () => { const messages = [{ content: "plain text" }, { text: "more text" }]; expect(outgoingMessagesHaveMarker(messages)).toBe(false); }); it("returns false for empty messages", () => { expect(outgoingMessagesHaveMarker([])).toBe(false); }); }); describe("provider payload marker fallback", () => { it("detects marker in string payload", () => { expect( providerPayloadHasMarker(`foo ${ROOT_PAIR_START_MARKER} bar`), ).toBe(true); }); it("detects marker in JSON-stringified object", () => { expect( providerPayloadHasMarker({ body: { text: ROOT_PAIR_START_MARKER } }), ).toBe(true); }); it("returns false for plain string without marker", () => { expect(providerPayloadHasMarker("plain payload")).toBe(false); }); it("returns false for null payload", () => { expect(providerPayloadHasMarker(null)).toBe(false); }); it("returns false for undefined payload", () => { expect(providerPayloadHasMarker(undefined)).toBe(false); }); }); describe("reinjection decision", () => { it("rejects reinjection for off mode", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "agent_start" }, "off", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("mode_does_not_require_reinjection"); }); it("rejects reinjection for advisory mode", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "agent_start" }, "advisory", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("mode_does_not_require_reinjection"); }); it("rejects reinjection when marker is present in messages", () => { const decision = shouldReinjectForEvent( { messages: [{ content: ROOT_PAIR_START_MARKER }], type: "agent_start", }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("marker_present_in_messages"); }); it("rejects reinjection when marker is present in payload", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], payload: ROOT_PAIR_START_MARKER, type: "agent_start", }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("marker_present_in_payload"); }); it("allows reinjection for agent_start in strong mode when marker absent", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "agent_start" }, "strong", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("relevant_turn_and_marker_absent"); }); it("allows reinjection for edit_intent in strict mode when marker absent", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "edit this file" }], type: "edit_intent" }, "strict", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("relevant_turn_and_marker_absent"); }); it("allows reinjection for architecture_sensitive in strong mode", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "discuss architecture" }], type: "architecture_sensitive", }, "strong", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("relevant_turn_and_marker_absent"); }); it("rejects reinjection for irrelevant event types", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "user_chat" }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("not_a_relevant_turn"); }); it("treats compaction as a relevant turn", () => { expect(isRelevantTurnForReinjection("compaction")).toBe(true); }); it("treats artifact_change as a relevant turn", () => { expect(isRelevantTurnForReinjection("artifact_change")).toBe(true); }); it("allows reinjection for compaction in strong mode when marker absent", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "compaction" }, "strong", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("relevant_turn_and_marker_absent"); }); it("rejects reinjection for compaction when marker is present", () => { const decision = shouldReinjectForEvent( { messages: [{ content: ROOT_PAIR_START_MARKER }], type: "compaction", }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("marker_present_in_messages"); }); it("forces reinjection for artifact_change even when marker is present", () => { const decision = shouldReinjectForEvent( { messages: [{ content: ROOT_PAIR_START_MARKER }], type: "artifact_change", }, "strong", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("root_pair_artifact_changed"); }); it("rejects reinjection for all relevant turn types in off mode", () => { const types = [ "agent_start", "edit_intent", "architecture_sensitive", "compaction", "artifact_change", ]; for (const type of types) { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type }, "off", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("mode_does_not_require_reinjection"); } }); it("rejects reinjection for all relevant turn types in advisory mode", () => { const types = [ "agent_start", "edit_intent", "architecture_sensitive", "compaction", "artifact_change", ]; for (const type of types) { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type }, "advisory", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("mode_does_not_require_reinjection"); } }); it("allows reinjection for strict mode on relevant turns", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "edit_intent" }, "strict", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("relevant_turn_and_marker_absent"); }); it("rejects reinjection in strict mode when marker is present", () => { const decision = shouldReinjectForEvent( { messages: [{ content: ROOT_PAIR_START_MARKER }], type: "architecture_sensitive", }, "strict", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("marker_present_in_messages"); }); it("forces reinjection for artifact_change in strict mode even with marker", () => { const decision = shouldReinjectForEvent( { messages: [{ content: ROOT_PAIR_START_MARKER }], type: "artifact_change", }, "strict", ); expect(decision.needed).toBe(true); expect(decision.reason).toBe("root_pair_artifact_changed"); }); it("rejects reinjection for unknown event types even in strong mode", () => { const decision = shouldReinjectForEvent( { messages: [{ content: "hi" }], type: "custom_event" }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("not_a_relevant_turn"); }); it("prefers message marker over payload marker when both present", () => { const decision = shouldReinjectForEvent( { messages: [{ content: ROOT_PAIR_START_MARKER }], payload: "no marker here", type: "edit_intent", }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("marker_present_in_messages"); }); it("falls back to payload when messages are empty", () => { const decision = shouldReinjectForEvent( { messages: [], payload: ROOT_PAIR_START_MARKER, type: "edit_intent", }, "strong", ); expect(decision.needed).toBe(false); expect(decision.reason).toBe("marker_present_in_payload"); }); }); describe("edit intent detection", () => { it("detects edit-the-file patterns", () => { expect(detectEditIntent([{ content: "edit the file" }])).toBe(true); expect(detectEditIntent([{ content: "modify this code" }])).toBe(true); expect(detectEditIntent([{ content: "update the function" }])).toBe(true); }); it("detects change-the-class patterns", () => { expect(detectEditIntent([{ content: "change the class" }])).toBe(true); expect(detectEditIntent([{ content: "refactor the module" }])).toBe(true); expect(detectEditIntent([{ content: "fix the bug" }])).toBe(true); }); it("detects write-new patterns", () => { expect(detectEditIntent([{ content: "write a file" }])).toBe(true); expect(detectEditIntent([{ content: "create a component" }])).toBe(true); expect(detectEditIntent([{ content: "generate a function" }])).toBe(true); expect(detectEditIntent([{ content: "write new file" }])).toBe(true); }); it("detects code-block edit intent", () => { expect( detectEditIntent([ { content: "```ts\nedit the function\n```", }, ]), ).toBe(true); }); it("detects apply-change patterns", () => { expect( detectEditIntent([{ content: "apply the change to fix this" }]), ).toBe(true); expect(detectEditIntent([{ content: "apply this patch" }])).toBe(true); }); it("returns false for non-edit content", () => { expect(detectEditIntent([{ content: "what is the weather?" }])).toBe( false, ); expect(detectEditIntent([{ content: "explain how this works" }])).toBe( false, ); expect(detectEditIntent([{ content: "hello world" }])).toBe(false); }); it("handles array content blocks", () => { expect( detectEditIntent([ { content: [ { type: "text", text: "please " }, { type: "text", text: "edit the file" }, ], }, ]), ).toBe(true); }); it("returns false for empty messages", () => { expect(detectEditIntent([])).toBe(false); }); it("detects delete/remove patterns", () => { expect(detectEditIntent([{ content: "delete the old method" }])).toBe( true, ); expect(detectEditIntent([{ content: "remove the unused code" }])).toBe( true, ); }); it("detects rewrite/patch patterns", () => { expect(detectEditIntent([{ content: "rewrite the component" }])).toBe( true, ); expect(detectEditIntent([{ content: "patch the file" }])).toBe(true); }); }); describe("architecture-sensitive reasoning detection", () => { it("detects architecture decision patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "this is an architecture decision" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "the architectural pattern we chose" }, ]), ).toBe(true); }); it("detects design pattern patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "design pattern review" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "design choice analysis" }, ]), ).toBe(true); }); it("detects restructure patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "restructure the codebase" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "reorganize the modules" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "redesign the API layer" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "rearchitect the system" }, ]), ).toBe(true); }); it("detects system design patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "system design overview" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "high-level architecture" }, ]), ).toBe(true); }); it("detects dependency patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "dependency injection strategy" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "dependency graph analysis" }, ]), ).toBe(true); }); it("detects API/interface design patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "API design migration" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "interface contract change" }, ]), ).toBe(true); }); it("detects architecture style patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "microservice architecture" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "clean architecture principles" }, ]), ).toBe(true); }); it("detects data and quality attribute patterns", () => { expect( detectArchitectureSensitiveReasoning([ { content: "data model design" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "scalability tradeoff decision" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "security concern analysis" }, ]), ).toBe(true); expect( detectArchitectureSensitiveReasoning([ { content: "performance concern review" }, ]), ).toBe(true); }); it("returns false for non-architecture content", () => { expect( detectArchitectureSensitiveReasoning([ { content: "what is the weather?" }, ]), ).toBe(false); expect( detectArchitectureSensitiveReasoning([ { content: "fix the typo in readme" }, ]), ).toBe(false); expect( detectArchitectureSensitiveReasoning([ { content: "add a console log" }, ]), ).toBe(false); }); it("handles array content blocks", () => { expect( detectArchitectureSensitiveReasoning([ { content: [ { type: "text", text: "let's discuss " }, { type: "text", text: "the architecture" }, ], }, ]), ).toBe(true); }); it("returns false for empty messages", () => { expect(detectArchitectureSensitiveReasoning([])).toBe(false); }); }); describe("protocol path detection", () => { it("detects protocol path when messages contain marker and trust boundary", () => { const messages = [ { content: `Start ${ROOT_PAIR_START_MARKER} ${TRUST_BOUNDARY_TEXT} end`, }, ]; expect(hasProtocolPath(messages)).toBe(true); }); it("detects protocol path when payload contains marker and trust boundary", () => { const payload = { body: `${ROOT_PAIR_START_MARKER}\n${TRUST_BOUNDARY_TEXT}`, }; expect(hasProtocolPath(undefined, payload)).toBe(true); }); it("returns false when only marker is present", () => { const messages = [{ content: `hello ${ROOT_PAIR_START_MARKER} world` }]; expect(hasProtocolPath(messages)).toBe(false); }); it("returns false when only trust boundary is present", () => { const messages = [{ content: `hello ${TRUST_BOUNDARY_TEXT} world` }]; expect(hasProtocolPath(messages)).toBe(false); }); it("returns false when neither marker nor trust boundary is present", () => { const messages = [{ content: "plain text" }]; expect(hasProtocolPath(messages)).toBe(false); }); it("returns false for empty messages and undefined payload", () => { expect(hasProtocolPath([])).toBe(false); expect(hasProtocolPath(undefined, undefined)).toBe(false); }); it("handles array content blocks", () => { const messages = [ { content: [ { type: "text", text: ROOT_PAIR_START_MARKER }, { type: "text", text: TRUST_BOUNDARY_TEXT }, ], }, ]; expect(hasProtocolPath(messages)).toBe(true); }); it("requires both elements in the same combined source", () => { const messages = [{ content: ROOT_PAIR_START_MARKER }]; const payload = { body: TRUST_BOUNDARY_TEXT }; expect(hasProtocolPath(messages, payload)).toBe(true); }); }); describe("advisory reminder", () => { it("returns a visible lightweight reminder with no synthetic artifacts", () => { const reminder = buildAdvisoryReminder(); expect(reminder).toContain("advisory mode active"); expect(reminder).toContain("📋"); expect(reminder).not.toContain(ROOT_PAIR_START_MARKER); expect(reminder).not.toContain("### Root index"); expect(reminder).not.toContain("### Root map"); }); }); describe("injection payload", () => { it("always includes the root pair", () => { const dir = mkdtempSync(join(tmpdir(), "pi-payload-test-")); writeFileSync(join(dir, ".pi-map.md"), "# root map"); writeFileSync(join(dir, ".pi-map.index.md"), "# root index"); const payload = buildInjectionPayload( dir, { contextBudgetPercent: 15, contextBudgetMaxTokens: 100_000 }, undefined, ); expect(payload.content).toContain(ROOT_PAIR_START_MARKER); expect(payload.content).toContain(ROOT_PAIR_END_MARKER); expect(payload.content).toContain("# root map"); expect(payload.content).toContain("# root index"); expect(payload.display).toBe(false); }); it("expands additional pairs when budget allows", () => { const dir = mkdtempSync(join(tmpdir(), "pi-payload-test-")); mkdirSync(join(dir, "src"), { recursive: true }); writeFileSync(join(dir, ".pi-map.md"), "# root map"); writeFileSync(join(dir, ".pi-map.index.md"), "# root index"); writeFileSync(join(dir, "src", ".pi-map.md"), "# src map"); writeFileSync(join(dir, "src", ".pi-map.index.md"), "# src index"); const payload = buildInjectionPayload( dir, { contextBudgetPercent: 100, contextBudgetMaxTokens: 10_000 }, 10_000, ); expect(payload.content).toContain("# src map"); expect(payload.content).toContain("# src index"); }); it("stops expanding when budget is exhausted", () => { const dir = mkdtempSync(join(tmpdir(), "pi-payload-test-")); mkdirSync(join(dir, "src"), { recursive: true }); writeFileSync(join(dir, ".pi-map.md"), "# root map"); writeFileSync(join(dir, ".pi-map.index.md"), "# root index"); writeFileSync( join(dir, "src", ".pi-map.md"), `# src map ${"x".repeat(400)}`, ); writeFileSync( join(dir, "src", ".pi-map.index.md"), `# src index ${"x".repeat(400)}`, ); const payload = buildInjectionPayload( dir, { contextBudgetPercent: 100, contextBudgetMaxTokens: 10 }, 10_000, ); expect(payload.content).toContain("# root map"); expect(payload.content).not.toContain("# src map x"); }); }); describe("sensitive action detection", () => { it("detects explicit edit_intent event type", () => { expect(isSensitiveAction("edit_intent", [])).toBe(true); }); it("detects explicit architecture_sensitive event type", () => { expect(isSensitiveAction("architecture_sensitive", [])).toBe(true); }); it("falls back to edit-intent heuristic for generic events", () => { expect( isSensitiveAction("user_chat", [{ content: "edit the file" }]), ).toBe(true); }); it("falls back to architecture heuristic for generic events", () => { expect( isSensitiveAction("user_chat", [ { content: "this is an architecture decision" }, ]), ).toBe(true); }); it("treats compaction as non-sensitive", () => { expect(isSensitiveAction("compaction", [{ content: "compacting" }])).toBe( false, ); }); it("treats agent_start as non-sensitive", () => { expect(isSensitiveAction("agent_start", [{ content: "hi" }])).toBe(false); }); it("returns false for irrelevant messages without event type", () => { expect( isSensitiveAction(undefined, [{ content: "what is the weather?" }]), ).toBe(false); }); it("returns false when no event type or messages are provided", () => { expect(isSensitiveAction(undefined, undefined)).toBe(false); }); it("handles array content blocks", () => { expect( isSensitiveAction(undefined, [ { content: [ { type: "text", text: "please " }, { type: "text", text: "refactor the module" }, ], }, ]), ).toBe(true); }); it("detects sensitive action from payload when messages are empty", () => { expect(isSensitiveAction("user_chat", [], "edit the file")).toBe(true); }); it("detects architecture-sensitive action from payload", () => { expect(isSensitiveAction("user_chat", [], "architecture decision")).toBe( true, ); }); it("returns false for irrelevant payload without messages or event type", () => { expect( isSensitiveAction(undefined, undefined, "what is the weather?"), ).toBe(false); }); it("combines messages and payload for heuristic detection", () => { expect( isSensitiveAction( "user_chat", [{ content: "please consider" }], "the architecture decision", ), ).toBe(true); }); }); describe("bypass marker parsing", () => { it("detects a valid bypass marker in messages", () => { expect( messagesHaveBypass([{ content: "[PI_MAP_BYPASS: temporary fix]" }]), ).toBe(true); }); it("detects a valid bypass marker prefix", () => { expect(BYPASS_MARKER_PREFIX).toBe("[PI_MAP_BYPASS:"); }); it("returns false when no marker is present", () => { expect(messagesHaveBypass([{ content: "plain text" }])).toBe(false); }); it("extracts the bypass reason", () => { expect( extractBypassReason([{ content: "[PI_MAP_BYPASS: temporary fix]" }]), ).toBe("temporary fix"); }); it("returns undefined when marker is absent", () => { expect(extractBypassReason([{ content: "plain text" }])).toBeUndefined(); }); it("rejects an empty bypass reason", () => { expect( extractBypassReason([{ content: "[PI_MAP_BYPASS:]" }]), ).toBeUndefined(); }); it("rejects a whitespace-only bypass reason", () => { expect( extractBypassReason([{ content: "[PI_MAP_BYPASS: ]" }]), ).toBeUndefined(); }); it("extracts the first reason when multiple markers are present", () => { expect( extractBypassReason([ { content: "[PI_MAP_BYPASS: first] [PI_MAP_BYPASS: second]", }, ]), ).toBe("first"); }); it("handles array content blocks", () => { expect( extractBypassReason([ { content: [ { type: "text", text: "[PI_MAP_BYPASS: " }, { type: "text", text: "array reason]" }, ], }, ]), ).toBe("array reason"); }); it("detects bypass marker in payload string", () => { expect( messagesHaveBypass(undefined, "[PI_MAP_BYPASS: payload reason]"), ).toBe(true); }); it("detects bypass marker in payload object", () => { expect( messagesHaveBypass(undefined, { body: "[PI_MAP_BYPASS: object reason]", }), ).toBe(true); }); it("extracts reason from payload when messages have none", () => { expect( extractBypassReason(undefined, "[PI_MAP_BYPASS: payload reason]"), ).toBe("payload reason"); }); it("prefers first marker across messages and payload", () => { expect( extractBypassReason( [{ content: "[PI_MAP_BYPASS: msg reason]" }], "[PI_MAP_BYPASS: payload reason]", ), ).toBe("msg reason"); }); it("extracts reason with colon inside", () => { expect( extractBypassReason([{ content: "[PI_MAP_BYPASS: fix: edge case]" }]), ).toBe("fix: edge case"); }); it("rejects bypass marker with only newlines/spaces", () => { expect( extractBypassReason([{ content: "[PI_MAP_BYPASS:\n\t ]" }]), ).toBeUndefined(); }); }); describe("strict bypass evaluation", () => { it("never guards in non-strict modes", () => { for (const mode of ["off", "advisory", "strong"] as const) { const decision = evaluateStrictBypass( { messages: [{ content: "edit the file" }], type: "edit_intent" }, mode, ); expect(decision.guard).toBe(false); } }); it("does not guard non-sensitive turns in strict mode", () => { const decision = evaluateStrictBypass( { messages: [{ content: "hi" }], type: "user_chat" }, "strict", ); expect(decision.guard).toBe(false); }); it("guards sensitive turns when protocol path is missing", () => { const decision = evaluateStrictBypass( { messages: [{ content: "edit the file" }], type: "edit_intent" }, "strict", ); expect(decision.guard).toBe(true); expect(decision.reason).toContain("protocol path missing"); }); it("does not guard sensitive turns when protocol path is present", () => { const decision = evaluateStrictBypass( { messages: [ { content: `${ROOT_PAIR_START_MARKER} ${TRUST_BOUNDARY_TEXT}`, }, ], type: "edit_intent", }, "strict", ); expect(decision.guard).toBe(false); }); it("uses heuristic fallback for generic sensitive events", () => { const decision = evaluateStrictBypass( { messages: [{ content: "refactor the module" }], type: "user_chat", }, "strict", ); expect(decision.guard).toBe(true); }); it("clears the guard when a valid inline bypass marker is present", () => { const decision = evaluateStrictBypass( { messages: [ { content: "[PI_MAP_BYPASS: emergency patch] edit the file", }, ], type: "edit_intent", }, "strict", ); expect(decision.guard).toBe(false); expect(decision.bypassMarker).toBe("emergency patch"); }); it("does not clear the guard for an empty bypass marker", () => { const decision = evaluateStrictBypass( { messages: [{ content: "[PI_MAP_BYPASS:] edit the file" }], type: "edit_intent", }, "strict", ); expect(decision.guard).toBe(true); }); it("does not guard when event is null or undefined", () => { expect(evaluateStrictBypass(undefined, "strict").guard).toBe(false); expect(evaluateStrictBypass(null, "strict").guard).toBe(false); }); it("does not guard when payload-only protocol path is present", () => { const decision = evaluateStrictBypass( { messages: [{ content: "edit the file" }], payload: `${ROOT_PAIR_START_MARKER}\n${TRUST_BOUNDARY_TEXT}`, type: "edit_intent", }, "strict", ); expect(decision.guard).toBe(false); }); it("clears guard when bypass marker is only in payload", () => { const decision = evaluateStrictBypass( { messages: [{ content: "edit the file" }], payload: "[PI_MAP_BYPASS: emergency patch]", type: "edit_intent", }, "strict", ); expect(decision.guard).toBe(false); expect(decision.bypassMarker).toBe("emergency patch"); }); it("guards when payload-only bypass marker has empty reason", () => { const decision = evaluateStrictBypass( { messages: [{ content: "edit the file" }], payload: "[PI_MAP_BYPASS:]", type: "edit_intent", }, "strict", ); expect(decision.guard).toBe(true); }); it("uses heuristic fallback for payload-only sensitive events", () => { const decision = evaluateStrictBypass( { messages: [], payload: "refactor the module", type: "user_chat", }, "strict", ); expect(decision.guard).toBe(true); }); }); describe("strict bypass guard message", () => { it("includes the provided reason", () => { const guard = buildStrictBypassGuard("custom reason"); expect(guard).toContain("custom reason"); expect(guard).toContain("🛑"); }); it("includes a default reason when none is provided", () => { const guard = buildStrictBypassGuard(); expect(guard).toContain("sensitive action"); expect(guard).toContain("protocol path"); }); it("tells the user how to proceed", () => { const guard = buildStrictBypassGuard(); expect(guard).toContain("PI_MAP_BYPASS"); expect(guard).toContain("restore the project-map context"); }); it("does not contain synthetic root-pair content", () => { const guard = buildStrictBypassGuard(); expect(guard).not.toContain(ROOT_PAIR_START_MARKER); expect(guard).not.toContain("### Root index"); expect(guard).not.toContain("### Root map"); }); }); });