feat(prompt): implement prompt injection slice 3

This commit is contained in:
2026-06-11 21:32:38 +02:00
parent 621434b6e8
commit 19666c900e
7 changed files with 1479 additions and 4 deletions
+555
View File
@@ -93,6 +93,7 @@ describe("pi-extension", () => {
it("registers session_start and before_agent_start events", () => {
expect(registeredEvents).toHaveProperty("session_start");
expect(registeredEvents).toHaveProperty("before_agent_start");
expect(registeredEvents).toHaveProperty("context");
});
});
@@ -411,5 +412,559 @@ describe("pi-extension", () => {
expect(result.message.display).toBe(false);
expect(result.message.content).toContain("root `.pi-map.index.md`");
});
it("skips reinjection when outgoing context already contains marker", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.before_agent_start;
const result = await handler(
{
messages: [
{ content: "<!-- PI_MAP_ROOT_PAIR_START --> already injected" },
],
type: "agent_start",
},
mockCtx,
);
expect(result).toEqual({});
});
it("reinjects when root-pair artifact changes since last check", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nOriginal\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.before_agent_start;
// First call establishes baseline mtimes and injects
await handler(null, mockCtx);
// Modify root map on disk
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nUpdated\n");
// Second call should detect the change and reinject
const result = await handler(null, mockCtx);
expect(result).toHaveProperty("message");
expect(result.message.content).toContain("Updated");
expect(result.message.content).toContain("TestIndex");
});
});
describe("context event", () => {
it("registers a context handler", () => {
expect(registeredEvents).toHaveProperty("context");
expect(typeof registeredEvents.context).toBe("function");
});
it("injects on relevant turn when marker is absent", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{ messages: [{ content: "edit this file" }], type: "edit_intent" },
mockCtx,
);
expect(result).toHaveProperty("message");
expect(result.message.content).toContain(
"<!-- PI_MAP_ROOT_PAIR_START -->",
);
expect(result.message.content).toContain("TestMap");
expect(result.message.content).toContain("TestIndex");
});
it("skips injection when marker is already present in messages", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{
messages: [{ content: "<!-- PI_MAP_ROOT_PAIR_START -->" }],
type: "edit_intent",
},
mockCtx,
);
expect(result).toEqual({});
});
it("skips injection when marker is present in provider payload", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{
messages: [{ content: "hi" }],
payload: { body: "<!-- PI_MAP_ROOT_PAIR_START -->" },
type: "edit_intent",
},
mockCtx,
);
expect(result).toEqual({});
});
it("skips injection for irrelevant event types", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
expect(result).toEqual({});
});
it("skips injection when mode is advisory", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "advisory" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{ messages: [{ content: "edit this" }], type: "edit_intent" },
mockCtx,
);
expect(result).toEqual({});
});
it("reinjects on compaction event when marker is absent", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{ messages: [{ content: "compacting" }], type: "compaction" },
mockCtx,
);
expect(result).toHaveProperty("message");
expect(result.message.content).toContain(
"<!-- PI_MAP_ROOT_PAIR_START -->",
);
expect(result.message.content).toContain("TestMap");
expect(result.message.content).toContain("TestIndex");
});
it("skips reinjection on compaction when marker is present", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTest\n");
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{
messages: [{ content: "<!-- PI_MAP_ROOT_PAIR_START -->" }],
type: "compaction",
},
mockCtx,
);
expect(result).toEqual({});
});
it("reinjects on artifact_change event even when marker is present", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const result = await handler(
{
messages: [{ content: "<!-- PI_MAP_ROOT_PAIR_START -->" }],
type: "artifact_change",
},
mockCtx,
);
expect(result).toHaveProperty("message");
expect(result.message.content).toContain("TestMap");
expect(result.message.content).toContain("TestIndex");
});
it("reinjects when root-pair artifact changes since last check", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nOriginal\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
// Initialize mtimes with an irrelevant turn (no injection)
await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
// Modify root map on disk
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nUpdated\n");
// Next call should detect the change and reinject even on an irrelevant turn
const result = await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
expect(result).toHaveProperty("message");
expect(result.message.content).toContain("Updated");
expect(result.message.content).toContain("TestIndex");
});
it("sequence: irrelevant turn skips, first relevant turn reinjects", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const skip = await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
expect(skip).toEqual({});
const inject = await handler(
{ messages: [{ content: "edit this file" }], type: "edit_intent" },
mockCtx,
);
expect(inject).toHaveProperty("message");
expect(inject.message.content).toContain("PI_MAP_ROOT_PAIR_START");
});
it("sequence: relevant turn reinjects, subsequent relevant turn skips when marker present", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const first = await handler(
{ messages: [{ content: "edit this file" }], type: "edit_intent" },
mockCtx,
);
expect(first).toHaveProperty("message");
expect(first.message.content).toContain("PI_MAP_ROOT_PAIR_START");
const second = await handler(
{
messages: [
{
content: `<!-- PI_MAP_ROOT_PAIR_START --> already injected`,
},
],
type: "edit_intent",
},
mockCtx,
);
expect(second).toEqual({});
});
it("sequence: edit_intent then compaction reinjects only when marker absent", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const edit = await handler(
{ messages: [{ content: "edit this file" }], type: "edit_intent" },
mockCtx,
);
expect(edit).toHaveProperty("message");
expect(edit.message.content).toContain("PI_MAP_ROOT_PAIR_START");
const compaction = await handler(
{
messages: [
{
content: `<!-- PI_MAP_ROOT_PAIR_START --> already injected`,
},
],
type: "compaction",
},
mockCtx,
);
expect(compaction).toEqual({});
});
it("sequence: architecture_sensitive after irrelevant turn reinjects once", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const skip = await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
expect(skip).toEqual({});
const first = await handler(
{
messages: [{ content: "discuss the architecture" }],
type: "architecture_sensitive",
},
mockCtx,
);
expect(first).toHaveProperty("message");
expect(first.message.content).toContain("PI_MAP_ROOT_PAIR_START");
const second = await handler(
{
messages: [
{
content: `<!-- PI_MAP_ROOT_PAIR_START --> already injected`,
},
],
type: "architecture_sensitive",
},
mockCtx,
);
expect(second).toEqual({});
});
it("sequence: artifact_change invalidation triggers reinjection even after skipped turns", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nOriginal\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
// Establish baseline mtimes with an irrelevant turn
await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
// Relevant turn that finds marker present skips reinjection
const skip = await handler(
{
messages: [
{
content: `<!-- PI_MAP_ROOT_PAIR_START --> injected`,
},
],
type: "edit_intent",
},
mockCtx,
);
expect(skip).toEqual({});
// Modify root artifacts on disk
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nUpdated\n");
// Even an irrelevant turn must reinject because artifacts changed
const result = await handler(
{ messages: [{ content: "hi" }], type: "user_chat" },
mockCtx,
);
expect(result).toHaveProperty("message");
expect(result.message.content).toContain("Updated");
expect(result.message.content).toContain("TestIndex");
});
it("sequence: strict mode reinjects on relevant turns same as strong mode", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strict" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const first = await handler(
{ messages: [{ content: "edit this file" }], type: "edit_intent" },
mockCtx,
);
expect(first).toHaveProperty("message");
expect(first.message.content).toContain("PI_MAP_ROOT_PAIR_START");
const second = await handler(
{
messages: [
{
content: `<!-- PI_MAP_ROOT_PAIR_START --> already injected`,
},
],
type: "architecture_sensitive",
},
mockCtx,
);
expect(second).toEqual({});
const third = await handler(
{ messages: [{ content: "compacting" }], type: "compaction" },
mockCtx,
);
expect(third).toHaveProperty("message");
expect(third.message.content).toContain("PI_MAP_ROOT_PAIR_START");
});
it("sequence: mixed relevant and irrelevant turns preserves deterministic behavior", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, ".pi-map.md"), "# .\n## role\nTestMap\n");
writeFileSync(
join(dir, ".pi-map.index.md"),
"# . (index)\n## role\nTestIndex\n",
);
writeFileSync(
join(dir, ".pi-project-map.json"),
JSON.stringify({ promptInjectionMode: "strong" }),
);
mockCtx.cwd = dir;
const handler = registeredEvents.context;
const turns = [
{ type: "user_chat", expectInject: false },
{ type: "edit_intent", expectInject: true },
{ type: "user_chat", expectInject: false },
{ type: "architecture_sensitive", expectInject: false }, // marker present from prior injection
{ type: "compaction", expectInject: false }, // marker still present
];
let markerPrefix = "";
for (const turn of turns) {
const result = await handler(
{
messages: [{ content: `${markerPrefix}${turn.type}` }],
type: turn.type,
},
mockCtx,
);
if (turn.expectInject) {
expect(result).toHaveProperty("message");
expect(result.message.content).toContain("PI_MAP_ROOT_PAIR_START");
markerPrefix = "<!-- PI_MAP_ROOT_PAIR_START --> ";
} else {
expect(result).toEqual({});
}
}
});
});
});