Skip to content

Commit 29390c9

Browse files
authored
fix(opencode): store active plan backing file outside workspace (#743)
The backing file used for edit-based plan submission was stored at .opencode/plans/_active-plan.md inside the workspace, causing it to appear in git status and editor file trees. It is now stored at ~/.plannotator/active/{project}/_active-plan.md alongside the version history. - Move getPlanBackingPath to derive path from project name under ~/.plannotator/active/ - Derive project name from ctx.directory basename via sanitizeTag at call site - Delete backing file on approval since it is no longer needed after the session ends - Update tests to reflect new path contract
1 parent b3f1deb commit 29390c9

2 files changed

Lines changed: 21 additions & 12 deletions

File tree

apps/opencode-plugin/index.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
*/
1717

1818
import { type Plugin, tool } from "@opencode-ai/plugin";
19-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
19+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
20+
import { homedir } from "os";
2021
import path from "path";
2122

2223
// OpenCode's @hono/node-server patches global.Response with a polyfill that
@@ -67,6 +68,7 @@ import { composeImproveContext } from "@plannotator/shared/pfm-reminder";
6768
import {
6869
stripConflictingPlanModeRules,
6970
} from "./plan-mode";
71+
import { sanitizeTag } from "@plannotator/shared/project";
7072
import {
7173
applyWorkflowConfig,
7274
isPlanningAgent,
@@ -127,12 +129,13 @@ interface PlanEdit {
127129
}
128130

129131
/**
130-
* Backing file for the current plan. Managed entirely by the plugin;
132+
* Backing file for the current plan. Stored outside the workspace in
133+
* `~/.plannotator/active/{project}/_active-plan.md` so it never appears
134+
* in git status or editor file trees. Managed entirely by the plugin;
131135
* the agent never sees or touches this file directly.
132136
*/
133-
export function getPlanBackingPath(directory: string): string {
134-
const planDir = path.join(directory, ".opencode", "plans");
135-
return path.join(planDir, "_active-plan.md");
137+
export function getPlanBackingPath(project: string): string {
138+
return path.join(homedir(), ".plannotator", "active", project, "_active-plan.md");
136139
}
137140

138141
/**
@@ -564,7 +567,8 @@ Use /plannotator-last or /plannotator-annotate for manual review, or set workflo
564567
}
565568

566569
// Read existing backing file (empty on first call)
567-
const backingPath = getPlanBackingPath(ctx.directory);
570+
const project = sanitizeTag(path.basename(ctx.directory)) || "_unknown";
571+
const backingPath = getPlanBackingPath(project);
568572
const backingDir = path.dirname(backingPath);
569573
mkdirSync(backingDir, { recursive: true });
570574

@@ -640,6 +644,9 @@ Use /plannotator-last or /plannotator-annotate for manual review, or set workflo
640644
server.stop();
641645

642646
if (result.approved) {
647+
// Clean up backing file after approval
648+
try { unlinkSync(backingPath); } catch { /* already gone */ }
649+
643650
const shouldSwitchAgent = result.agentSwitch && result.agentSwitch !== 'disabled';
644651
const targetAgent = result.agentSwitch || 'build';
645652

apps/opencode-plugin/submit-plan.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { describe, expect, test } from "bun:test";
2+
import { homedir } from "os";
3+
import path from "path";
24
import {
35
applyEdits,
46
formatWithLineNumbers,
@@ -173,14 +175,14 @@ describe("formatWithLineNumbers", () => {
173175
// ── getPlanBackingPath ─────────────────────────────────────────────────────
174176

175177
describe("getPlanBackingPath", () => {
176-
test("returns path inside .opencode/plans within the given directory", () => {
177-
const result = getPlanBackingPath("/some/project");
178-
expect(result).toBe("/some/project/.opencode/plans/_active-plan.md");
178+
test("returns path inside ~/.plannotator/active/{project}/_active-plan.md", () => {
179+
const result = getPlanBackingPath("myproject");
180+
expect(result).toBe(path.join(homedir(), ".plannotator", "active", "myproject", "_active-plan.md"));
179181
});
180182

181-
test("uses the provided directory as the root", () => {
182-
const result = getPlanBackingPath("/home/user/myproject");
183-
expect(result).toContain("/home/user/myproject");
183+
test("uses the provided project name as the directory segment", () => {
184+
const result = getPlanBackingPath("some-project");
185+
expect(result).toContain(path.join(".plannotator", "active", "some-project"));
184186
expect(result).toContain("_active-plan.md");
185187
});
186188
});

0 commit comments

Comments
 (0)