Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions apps/opencode-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { loadConfig, resolveSharingEnabled } from "@plannotator/shared/config";
import { readImprovementHook } from "@plannotator/shared/improvement-hooks";
import { composeImproveContext } from "@plannotator/shared/pfm-reminder";
import {
composeSystemPrompt,
stripConflictingPlanModeRules,
} from "./plan-mode";
import {
Expand Down Expand Up @@ -406,32 +407,32 @@ tools (except writing markdown files), or otherwise make changes to the system.

if (shouldInjectFullPlanningPrompt(lastUserAgent, workflowOptions)) {
const stripped = stripConflictingPlanModeRules(output.system);
output.system.length = 0;
output.system.push(...stripped);
output.system.push(getPlanningPrompt());

const hook = readImprovementHook("enterplanmode-improve");
const pfmEnabled = loadConfig().pfmReminder === true;
const improveContext = composeImproveContext({
pfmEnabled,
improvementHookContent: hook?.content ?? null,
});
if (improveContext) {
output.system.push(improveContext);
}
const parts = [...stripped, getPlanningPrompt()];
if (improveContext) parts.push(improveContext);
output.system.length = 0;
output.system.push(...composeSystemPrompt([], parts));

return;
}

if (!shouldInjectGenericPlanReminder(lastUserAgent, isSubagent, workflowOptions)) return;

output.system.push(`## Plan Submission
const planSubmissionReminder = `## Plan Submission

When you have completed your plan, call the \`submit_plan\` tool to submit it for user review. Pass your full plan as a single edit: \`{ "edits": [{ "start": 1, "content": "..." }] }\`.

The user will review your plan in a visual UI where they can annotate, approve, or request changes. If rejected, the response includes your plan with line numbers; use targeted edits to revise specific sections.

Do NOT proceed with implementation until your plan is approved.`);
Do NOT proceed with implementation until your plan is approved.`;
const composed = composeSystemPrompt(output.system, [planSubmissionReminder]);
output.system.length = 0;
output.system.push(...composed);
},

// Intercept plannotator commands before the agent sees them.
Expand Down
15 changes: 15 additions & 0 deletions apps/opencode-plugin/plan-mode.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import {
composeSystemPrompt,
normalizeEditPermission,
stripConflictingPlanModeRules,
} from "./plan-mode";
Expand Down Expand Up @@ -72,3 +73,17 @@ describe("stripConflictingPlanModeRules", () => {
).toEqual(["Keep the plan concise."]);
});
});

describe("composeSystemPrompt", () => {
test.each([
["always one element", ["a"], ["b"], ["a\n\nb"]],
["empty strip case", [], ["prompt"], ["prompt"]],
["multi-element order", ["base", "extra"], ["add1", "add2"], ["base\n\nextra\n\nadd1\n\nadd2"]],
["content order (plan path)", ["strip1"], ["plan", "improve"], ["strip1\n\nplan\n\nimprove"]],
["trailing newlines trimmed", ["base\n"], ["add\n\n"], ["base\n\nadd"]],
["empty string entry collapses", ["", "a"], ["b"], ["a\n\nb"]],
["empty inputs", [], [], [""]],
])("%s", (_name, system, additions, expected) => {
expect(composeSystemPrompt(system, additions)).toEqual(expected);
});
});
4 changes: 4 additions & 0 deletions apps/opencode-plugin/plan-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ export function stripConflictingPlanModeRules(systemEntries: string[]): string[]
)
.filter(Boolean);
}

export function composeSystemPrompt(system: string[], additions: string[]): string[] {
return [system.concat(additions).map((s) => s.trim()).filter(Boolean).join("\n\n")];
}