@janvitos/pi-plan-build 0.1.37 → 0.1.39

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.
package/README.md CHANGED
@@ -91,7 +91,7 @@ Both actions leave Plan mode active, stop the agent, and wait for the next user
91
91
 
92
92
  When Pi uses `"tuiMode": "fullscreen"` and the saved plan contains top-level `- [ ]` items under `## Implementation Steps`, `plan_exit` offers the additional **Implement step by step** approval action. This is opt-in per plan; it does not replace either one-shot implementation option or **Stay in Plan mode**.
93
93
 
94
- The passive 64-column right panel reserves terminal columns, so the transcript and editor reflow instead of being covered. Long step instructions wrap across aligned continuation rows rather than being clipped. It never accepts focus or keyboard input and collapses below 132 terminal columns. The panel is a visual status aid only; all control happens through ordinary prompts. The agent interprets intent contextually, so these are examples rather than required commands:
94
+ The passive 64-column right panel reserves terminal columns, so the transcript and editor reflow instead of being covered. Long step instructions and panel guidance wrap instead of being clipped. It never accepts focus or keyboard input and collapses below 132 terminal columns. The panel is a visual status aid only; all control happens through ordinary prompts. Its guidance remains visible, and the main transcript says, “Awaiting your instructions.” The agent interprets intent contextually, so these are examples rather than required commands:
95
95
 
96
96
  - “Implement the next step,” “Start step 2,” or simply “Approved” / “Go ahead.”
97
97
  - “Step 1 is complete,” “I verified that one,” or “I already handled this.”
@@ -99,7 +99,7 @@ The passive 64-column right panel reserves terminal columns, so the transcript a
99
99
  - “Pause the plan,” “hide the plan,” or “show the plan.”
100
100
  - “Cancel this plan” at any point to end step-by-step execution immediately.
101
101
 
102
- The extension exposes these actions to the agent through `plan_step_control`; project mutations remain blocked until the user clearly approves a ready step or explicitly indicates that it is already complete. A ready step may be marked complete without implementation when the user says they already handled or verified it. An implemented step is marked completed immediately after verification, and the next step becomes ready without a review or acceptance phase. The agent interprets intent contextually rather than requiring exact phrases, while the extension validates every resulting state transition. Step tool results identify the affected step, for example `Step 2: complete`, rather than presenting a plan-wide completion label. When the final step is completed or skipped, the panel and execution guards are removed immediately and the main window receives a completion confirmation with a concise plan summary. Cancelling removes the panel and execution guards immediately, restores the full-width layout, and preserves the saved plan file for reference. The agent implements only that step, calls `plan_step_complete`, and waits for the user's next prompt. Progress, revisions, summaries, and panel visibility survive reload/resume. If such a session is opened in regular mode, progress is retained but cannot advance until fullscreen mode is restored; no overlay fallback is used.
102
+ The extension exposes these actions to the agent through `plan_step_control`; project mutations remain blocked until the user clearly approves a ready step or explicitly indicates that it is already complete. A ready step may be marked complete without implementation when the user says they already handled or verified it. An implemented step is marked completed immediately after verification, and the next step becomes ready without a review or acceptance phase. The agent interprets intent contextually rather than requiring exact phrases, while the extension validates every resulting state transition. Step tool results identify the affected step, for example `Step 2: complete`, rather than presenting a plan-wide completion label. When the final step is completed or skipped, the panel and execution guards are removed immediately and the main window receives a Markdown-formatted completion summary with plan-style headings, spacing, and colors. Cancelling removes the panel and execution guards immediately, restores the full-width layout, and preserves the saved plan file for reference. The agent implements only that step, calls `plan_step_complete`, and waits for the user's next prompt. Progress, revisions, summaries, and panel visibility survive reload/resume. If such a session is opened in regular mode, progress is retained but cannot advance until fullscreen mode is restored; no overlay fallback is used.
103
103
 
104
104
  Enable fullscreen in `~/.pi/agent/settings.json` and restart Pi:
105
105
 
package/index.ts CHANGED
@@ -53,6 +53,7 @@ import {
53
53
  PLAN_EXIT_FRESH_CHOICE,
54
54
  PLAN_EXIT_STAY_ACKNOWLEDGEMENT,
55
55
  PLAN_EXIT_STAY_CHOICE,
56
+ PLAN_STEP_READY_ACKNOWLEDGEMENT,
56
57
  renderModeComposer,
57
58
  type Mode,
58
59
  unique,
@@ -64,6 +65,7 @@ const PLAN_REVIEW_ENTRY_TYPE = "pi-plan-build-review";
64
65
  const LEGACY_PLAN_REVIEW_ENTRY_TYPE = "opencode-plan-review";
65
66
  const MODE_NOTICE_ENTRY_TYPE = "pi-plan-build-notice";
66
67
  const LEGACY_MODE_NOTICE_ENTRY_TYPE = "opencode-mode-notice";
68
+ const PLAN_STEP_GUIDANCE_ENTRY_TYPE = "pi-plan-build-step-guidance";
67
69
  const STATUS_KEY = "pi-plan-build-mode";
68
70
  const PLAN_STEP_CHOICE = "Implement step by step";
69
71
  const PANEL_WIDTH = 64;
@@ -119,10 +121,13 @@ export default function planBuildModes(pi: ExtensionAPI): void {
119
121
  const message = typeof entry.data?.message === "string" ? entry.data.message : "Plan mode unchanged.";
120
122
  return new Text(theme.fg("warning", message), 0, 0);
121
123
  };
124
+ const renderPlanStepGuidance: EntryRenderer = (_entry, _options, theme) =>
125
+ new Text(theme.fg("success", PLAN_STEP_READY_ACKNOWLEDGEMENT), 0, 0);
122
126
  pi.registerEntryRenderer<{ plan: string }>(PLAN_REVIEW_ENTRY_TYPE, renderPlanReview);
123
127
  pi.registerEntryRenderer<{ plan: string }>(LEGACY_PLAN_REVIEW_ENTRY_TYPE, renderPlanReview);
124
128
  pi.registerEntryRenderer<{ message: string }>(MODE_NOTICE_ENTRY_TYPE, renderModeNotice);
125
129
  pi.registerEntryRenderer<{ message: string }>(LEGACY_MODE_NOTICE_ENTRY_TYPE, renderModeNotice);
130
+ pi.registerEntryRenderer(PLAN_STEP_GUIDANCE_ENTRY_TYPE, renderPlanStepGuidance);
126
131
 
127
132
  function stateData(): StoredState {
128
133
  return { version: 1, selectedMode, pendingReminder, toolsBeforeModes, ...(execution ? { execution } : {}) };
@@ -412,9 +417,9 @@ export default function planBuildModes(pi: ExtensionAPI): void {
412
417
  const target = params.step === undefined
413
418
  ? execution.steps.find((step) => step.status === "ready")
414
419
  : execution.steps[Math.floor(params.step) - 1];
415
- const finish = (message: string) => ({
420
+ const finish = (message: string, extraDetails?: { planCompleted?: boolean }) => ({
416
421
  content: [{ type: "text" as const, text: message }],
417
- details: { action: params.action, stepId: target?.id },
422
+ details: { action: params.action, stepId: target?.id, ...extraDetails },
418
423
  terminate: true,
419
424
  });
420
425
 
@@ -443,11 +448,17 @@ export default function planBuildModes(pi: ExtensionAPI): void {
443
448
  }
444
449
  if (params.action === "complete") {
445
450
  const completion = applyExecutionTransition(completePlanStep(execution, target.id));
446
- return finish(completion ?? "The step was marked complete. The next step is ready and awaits user instruction.");
451
+ return finish(
452
+ completion ?? "The step was marked complete. The next step is ready and awaits user instruction.",
453
+ { planCompleted: completion !== undefined },
454
+ );
447
455
  }
448
456
  if (params.action === "skip") {
449
457
  const completion = applyExecutionTransition(skipPlanStep(execution, target.id));
450
- return finish(completion ?? "The step was skipped. The next step awaits user instruction.");
458
+ return finish(
459
+ completion ?? "The step was skipped. The next step awaits user instruction.",
460
+ { planCompleted: completion !== undefined },
461
+ );
451
462
  }
452
463
  if (!params.instruction?.trim()) throw new Error("Revising a step requires a replacement instruction");
453
464
  const plan = await fs.promises.readFile(planPath, "utf8");
@@ -466,6 +477,8 @@ export default function planBuildModes(pi: ExtensionAPI): void {
466
477
  },
467
478
  renderResult(result, _options, theme, context) {
468
479
  const text = result.content.find((item) => item.type === "text")?.text ?? "Plan state updated";
480
+ const details = result.details as { planCompleted?: boolean } | undefined;
481
+ if (details?.planCompleted && !context.isError) return new Markdown(text, 0, 0, getMarkdownTheme());
469
482
  return new Text(theme.fg(context.isError ? "error" : "success", text), 0, 0);
470
483
  },
471
484
  });
@@ -484,7 +497,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
484
497
  const completion = applyExecutionTransition(completePlanStep(execution, step.id, params.summary));
485
498
  return {
486
499
  content: [{ type: "text", text: completion ?? "The step was completed. The next step is ready and awaits user instruction." }],
487
- details: { stepId: step.id, completed: true },
500
+ details: { stepId: step.id, completed: true, planCompleted: completion !== undefined },
488
501
  terminate: true,
489
502
  };
490
503
  },
@@ -493,6 +506,8 @@ export default function planBuildModes(pi: ExtensionAPI): void {
493
506
  },
494
507
  renderResult(result, _options, theme, context) {
495
508
  const text = result.content.find((item) => item.type === "text")?.text ?? "Plan step completed";
509
+ const details = result.details as { planCompleted?: boolean } | undefined;
510
+ if (details?.planCompleted && !context.isError) return new Markdown(text, 0, 0, getMarkdownTheme());
496
511
  return new Text(theme.fg(context.isError ? "error" : "success", text), 0, 0);
497
512
  },
498
513
  });
@@ -541,6 +556,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
541
556
  await selectMode("build", ctx, "tool");
542
557
  updateExecution(stepExecution);
543
558
  ensurePanelLayout();
559
+ pi.appendEntry(PLAN_STEP_GUIDANCE_ENTRY_TYPE);
544
560
  return {
545
561
  content: [{ type: "text", text: "Step-by-step execution is ready. Stop now and wait for the user's natural-language instruction in the composer; the plan panel is visual-only." }],
546
562
  details: { approved: true, action: "step-by-step", mode: "build", planPath },
@@ -588,7 +604,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
588
604
  renderResult(result, _options, theme, context) {
589
605
  const details = result.details as { approved?: boolean; action?: string } | undefined;
590
606
  if (details?.action === "step-by-step" && !context.isError) {
591
- return new Text(theme.fg("success", "Step-by-step execution ready — waiting for your instruction."), 0, 0);
607
+ return new Text(theme.fg("success", "Step-by-step execution ready"), 0, 0);
592
608
  }
593
609
  if (details?.action === "implement-fresh" && !context.isError) {
594
610
  return new Text(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@janvitos/pi-plan-build",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "Plan safely, approve explicitly, then implement here or in a clean session.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/plan-execution.ts CHANGED
@@ -114,11 +114,12 @@ function makeNextReady(state: PlanExecutionState, afterId: string): void {
114
114
  }
115
115
 
116
116
  export function formatPlanCompletionSummary(state: PlanExecutionState): string {
117
- const lines = ["Plan complete.", "", "Summary:"];
117
+ const lines = ["# Plan complete", "", "## Summary", ""];
118
118
  for (const [index, step] of state.steps.entries()) {
119
119
  const outcome = step.status === "skipped" ? "Skipped" : "Completed";
120
- lines.push(`${index + 1}. ${outcome}: ${step.text}`);
121
- if (step.summary?.trim()) lines.push(` ${step.summary.trim()}`);
120
+ lines.push(`${index + 1}. **${outcome}:** ${step.text}`);
121
+ if (step.summary?.trim()) lines.push("", ` ${step.summary.trim()}`);
122
+ if (index < state.steps.length - 1) lines.push("");
122
123
  }
123
124
  return lines.join("\n");
124
125
  }
package/plan-panel.ts CHANGED
@@ -34,6 +34,9 @@ export class PlanPanel implements Component {
34
34
  const clipped = truncateToWidth(content, contentWidth, "");
35
35
  return `${border("│")} ${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))} ${border("│")}`;
36
36
  };
37
+ const pushWrapped = (lines: string[], content: string) => {
38
+ for (const line of wrapTextWithAnsi(content, contentWidth)) lines.push(pad(line));
39
+ };
37
40
  const done = this.state.steps.filter((step) => step.status === "completed" || step.status === "skipped").length;
38
41
  const status = this.state.status === "completed" ? "complete" : this.state.status;
39
42
  const currentIndex = this.state.steps.findIndex((step) => step.id === this.state.selectedStepId);
@@ -65,8 +68,8 @@ export class PlanPanel implements Component {
65
68
  }
66
69
  lines.push(border(`├${"─".repeat(inner)}┤`));
67
70
  if (this.state.status === "completed") lines.push(pad(this.theme.fg("success", "Plan complete")));
68
- else if (this.state.status === "paused") lines.push(pad("Tell the agent to resume, cancel, or hide the plan."));
69
- else if (current?.status === "ready") lines.push(pad("Tell the agent to implement, complete, edit, skip, cancel, or hide this step."));
71
+ else if (this.state.status === "paused") pushWrapped(lines, "Tell the agent to resume, cancel, or hide the plan.");
72
+ else if (current?.status === "ready") pushWrapped(lines, "Tell the agent to implement, complete, edit, or skip steps. You can also cancel or hide the plan.");
70
73
  lines.push(border(`╰${"─".repeat(inner)}╯`));
71
74
  return lines.map((line) => truncateToWidth(line, safeWidth, ""));
72
75
  }
package/utils.ts CHANGED
@@ -136,6 +136,7 @@ export const PLAN_EXIT_FRESH_CHOICE = "Start fresh and implement";
136
136
  export const PLAN_EXIT_STAY_CHOICE = "Stay in Plan mode";
137
137
  export const PLAN_EXIT_STAY_ACKNOWLEDGEMENT =
138
138
  "Staying in Plan mode. Let me know when you’re ready to revise or implement the plan.";
139
+ export const PLAN_STEP_READY_ACKNOWLEDGEMENT = "Awaiting your instructions.";
139
140
 
140
141
  export type PlanExitDecision = "implement-here" | "implement-fresh" | "stay";
141
142