open-plan-annotator 1.0.8 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@
12
12
  "name": "open-plan-annotator",
13
13
  "source": "./",
14
14
  "description": "Interactive plan annotation UI: review, strikethrough, and comment on Claude's plans before approving. Fully local, no external services.",
15
- "version": "1.0.8",
15
+ "version": "1.0.11",
16
16
  "author": {
17
17
  "name": "ndom91"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "open-plan-annotator",
3
3
  "description": "Interactive plan annotation UI: review, strikethrough, and comment on Claude's plans before approving. Fully local, no external services.",
4
- "version": "1.0.8",
4
+ "version": "1.0.11",
5
5
  "author": {
6
6
  "name": "ndom91"
7
7
  },
@@ -37,6 +37,7 @@ try {
37
37
  stdinBuffer = Buffer.alloc(0);
38
38
  }
39
39
 
40
+ let justInstalled = false;
40
41
  if (!fs.existsSync(binaryPath)) {
41
42
  // Auto-download the binary (handles pnpm blocking postinstall)
42
43
  console.error("open-plan-annotator: binary not found, downloading...");
@@ -59,10 +60,15 @@ if (!fs.existsSync(binaryPath)) {
59
60
  );
60
61
  process.exit(1);
61
62
  }
63
+ justInstalled = true;
62
64
  }
63
65
 
64
66
  // Handle `open-plan-annotator update` subcommand
65
67
  if (process.argv[2] === "update") {
68
+ if (justInstalled) {
69
+ console.log("Binary installed (v" + VERSION + ")");
70
+ process.exit(0);
71
+ }
66
72
  try {
67
73
  execFileSync(binaryPath, ["update"], {
68
74
  stdio: "inherit",
package/opencode/index.js CHANGED
@@ -4,15 +4,44 @@ import { resolveImplementationHandoff } from "./config.js";
4
4
 
5
5
  const PLAN_REVIEW_INSTRUCTIONS = `## Plan Review Workflow
6
6
 
7
- For non-trivial implementation work, create a plan first and call the \`submit_plan\` tool.
8
- The user will review the plan in a browser UI and either approve it or request changes.
9
-
10
- - If the tool returns that the plan was **approved**: immediately begin writing code. Do NOT call \`submit_plan\` again — the plan phase is complete.
11
- - If the tool returns **revision feedback**: revise the plan based on the feedback, then call \`submit_plan\` again with the updated plan.
12
-
13
- Only call \`submit_plan\` once per plan version. After approval, your sole job is to implement what was approved.`;
14
-
15
- const IMPLEMENTATION_PROMPT = "The plan has been approved by the user. Begin implementing it now — write code, create files, and make changes as described in the plan. Do not re-submit or re-review the plan.";
7
+ Track planning/execution using this state enum:
8
+ - \`DISCOVERY\`, \`PLAN_DRAFT\`, \`AWAITING_PLAN_DECISION\`, \`EXECUTION\`, \`DONE\`
9
+
10
+ State transitions:
11
+ - Start in \`DISCOVERY\`.
12
+ - Move to \`PLAN_DRAFT\` only when a plan is required.
13
+ - From \`PLAN_DRAFT\`, call \`submit_plan\` exactly once, then move to \`AWAITING_PLAN_DECISION\`.
14
+ - If user approves plan, set \`plan_status=approved\` and move to \`EXECUTION\`.
15
+ - If user rejects or requests plan changes, set \`plan_status=rejected\` and return to \`PLAN_DRAFT\`.
16
+ - When work is complete, move to \`DONE\`.
17
+
18
+ Required flags:
19
+ - \`plan_status\` in \`{none, submitted, approved, rejected}\`
20
+ - \`explicit_replan\` in \`{true,false}\` (default \`false\`)
21
+ - Set \`explicit_replan=true\` only when user clearly asks to replan (for example: revise/change/new/update plan).
22
+
23
+ Hard rules:
24
+ 1) \`submit_plan\` is allowed only in \`PLAN_DRAFT\`.
25
+ 2) If \`plan_status=approved\`, \`submit_plan\` is forbidden unless \`explicit_replan=true\`.
26
+ 3) Call \`submit_plan\` at most once per plan draft/version. If rejected, revise and submit once for the new draft.
27
+ 4) After approval, treat follow-up user messages as execution refinements by default, not planning triggers.
28
+ 5) On conflict, prioritize the approved plan and execute immediately.
29
+ 6) Do not ask permission to proceed after approval; execute and report progress/results.
30
+ 7) When delegating to subagents, always pass current \`plan_status\` and \`explicit_replan\` values.
31
+ 8) If \`plan_status=approved\` and \`explicit_replan=false\`, subagents must execute and must not call \`submit_plan\`.
32
+
33
+ Tool guard before calling \`submit_plan\`:
34
+ - assert \`state == PLAN_DRAFT\`
35
+ - assert \`plan_status != approved || explicit_replan == true\`
36
+ - if an assertion fails, continue execution without submitting a new plan.`;
37
+
38
+ const IMPLEMENTATION_PROMPT = [
39
+ "Plan review status: plan_status=approved.",
40
+ "State transition: next_state=EXECUTION.",
41
+ "Replan intent: explicit_replan=false unless the user explicitly asks to revise the plan.",
42
+ "Execute the approved plan directly now — write code, create files, and make changes.",
43
+ "Do not call `submit_plan` again unless the user explicitly requests re-planning.",
44
+ ].join(" ");
16
45
 
17
46
  function getErrorMessage(error) {
18
47
  if (error instanceof Error && error.message) {
@@ -133,7 +162,7 @@ export const OpenPlanAnnotatorPlugin = async (ctx) => {
133
162
  tool: {
134
163
  submit_plan: tool({
135
164
  description:
136
- "Submit a markdown plan for interactive user review. Returns approval status or structured revision feedback.",
165
+ "Submit a markdown plan for interactive user review. Returns a structured result with plan_status, next_state, approved, and feedback fields.",
137
166
 
138
167
  args: {
139
168
  plan: tool.schema.string().describe("The complete implementation plan in markdown format"),
@@ -147,6 +176,13 @@ export const OpenPlanAnnotatorPlugin = async (ctx) => {
147
176
  cwd: ctx.directory,
148
177
  });
149
178
 
179
+ const basePayload = {
180
+ plan_status: result.approved ? "approved" : "rejected",
181
+ next_state: result.approved ? "EXECUTION" : "PLAN_DRAFT",
182
+ approved: result.approved,
183
+ feedback: result.approved ? null : (result.feedback ?? "Plan changes requested."),
184
+ };
185
+
150
186
  if (result.approved) {
151
187
  const lines = [
152
188
  "Plan approved by the user.",
@@ -167,18 +203,24 @@ export const OpenPlanAnnotatorPlugin = async (ctx) => {
167
203
  }
168
204
 
169
205
  lines.push("Begin implementing the approved plan now — write code and make changes.");
170
- return lines.join("\n\n");
206
+ return {
207
+ ...basePayload,
208
+ guidance: lines.join("\n\n"),
209
+ };
171
210
  }
172
211
 
173
- return [
174
- "Plan needs revision.",
175
- "",
176
- "## User feedback",
177
- "",
178
- result.feedback ?? "Plan changes requested.",
179
- "",
180
- "Revise the plan using this feedback, then call `submit_plan` again.",
181
- ].join("\n");
212
+ return {
213
+ ...basePayload,
214
+ guidance: [
215
+ "Plan needs revision.",
216
+ "",
217
+ "## User feedback",
218
+ "",
219
+ basePayload.feedback,
220
+ "",
221
+ "Revise the plan using this feedback, then submit the revised draft once via `submit_plan`.",
222
+ ].join("\n"),
223
+ };
182
224
  },
183
225
  }),
184
226
  },
@@ -0,0 +1,56 @@
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ afterEach(() => {
4
+ mock.restore();
5
+ });
6
+
7
+ function createPluginContext() {
8
+ return {
9
+ directory: process.cwd(),
10
+ client: {
11
+ session: {
12
+ messages: async () => ({ data: [] }),
13
+ prompt: async () => ({ data: null }),
14
+ },
15
+ app: {
16
+ agents: async () => ({ data: [] }),
17
+ },
18
+ },
19
+ };
20
+ }
21
+
22
+ describe("submit_plan return schema contract", () => {
23
+ test("approved decision maps to approved execution payload", async () => {
24
+ mock.module("./bridge.js", () => ({
25
+ runPlanReview: async () => ({ approved: true }),
26
+ }));
27
+
28
+ const { OpenPlanAnnotatorPlugin } = await import(`./index.js?approved-${Date.now()}`);
29
+ const plugin = await OpenPlanAnnotatorPlugin(createPluginContext());
30
+
31
+ const result = await plugin.tool.submit_plan.execute({ plan: "# Plan" }, { sessionID: "session-1" });
32
+
33
+ expect(result.plan_status).toBe("approved");
34
+ expect(result.next_state).toBe("EXECUTION");
35
+ expect(result.approved).toBe(true);
36
+ expect(result.feedback).toBeNull();
37
+ });
38
+
39
+ test("rejected decision maps to plan redraft payload with bridge feedback", async () => {
40
+ const bridgeFeedback = "Need to add rollback steps.";
41
+
42
+ mock.module("./bridge.js", () => ({
43
+ runPlanReview: async () => ({ approved: false, feedback: bridgeFeedback }),
44
+ }));
45
+
46
+ const { OpenPlanAnnotatorPlugin } = await import(`./index.js?rejected-${Date.now()}`);
47
+ const plugin = await OpenPlanAnnotatorPlugin(createPluginContext());
48
+
49
+ const result = await plugin.tool.submit_plan.execute({ plan: "# Plan" }, { sessionID: "session-2" });
50
+
51
+ expect(result.plan_status).toBe("rejected");
52
+ expect(result.next_state).toBe("PLAN_DRAFT");
53
+ expect(result.approved).toBe(false);
54
+ expect(result.feedback).toBe(bridgeFeedback);
55
+ });
56
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-plan-annotator",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "description": "Fully local plugin for interactive plan annotation from your Agentic assistants",
6
6
  "author": "ndom91",