pi-plans 0.1.1 → 0.2.0
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 +47 -15
- package/index.ts +143 -95
- package/package.json +1 -1
- package/references/pi-planning-workflow.md +6 -3
- package/references/state-and-config.md +1 -1
- package/src/autocomplete.ts +163 -0
- package/src/compaction.ts +502 -0
- package/src/exec.ts +1098 -95
- package/src/plan.ts +143 -0
- package/src/query-hook.ts +82 -0
- package/src/refine-ui-helpers.ts +89 -0
- package/src/refine-ui-state.ts +78 -0
- package/src/refine-ui.ts +322 -0
- package/src/state.ts +19 -1
- package/src/subagent.ts +196 -69
- package/tests/autocomplete.test.ts +142 -0
- package/tests/compaction.test.ts +74 -0
- package/tests/exec.test.ts +803 -101
- package/tests/execute-plan.test.ts +65 -0
- package/tests/plan.test.ts +94 -1
- package/tests/plans.test.ts +37 -0
- package/tests/query-hook.test.ts +82 -0
- package/tests/refine-ui.test.ts +127 -0
- package/tests/state.test.ts +57 -0
- package/tests/subagent.test.ts +114 -0
- package/tools/ask-choice.ts +22 -0
- package/tools/execute-plan.ts +13 -5
- package/tools/plans.ts +16 -0
- package/tools/refine.ts +125 -71
- package/src/execution-panel.ts +0 -499
- package/tests/execution-panel.test.ts +0 -205
package/README.md
CHANGED
|
@@ -7,15 +7,18 @@
|
|
|
7
7
|
<h2 align="center"><b>Plan. Review. Execute.</b></h2>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<i>Versioned, reviewed Markdown plans land before any code changes.<br>Human-in-the-loop planning for the Pi coding agent
|
|
10
|
+
<i>Versioned, reviewed Markdown plans land before any code changes.<br>Human-in-the-loop planning for the <a href="https://github.com/earendil-works/pi">Pi coding agent</a>.</i>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://github.com/MaxInGaussian/pi-plans/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/MaxInGaussian/pi-plans?style=square" /></a>
|
|
15
15
|
<a href="https://hits.sh/github.com/MaxInGaussian/pi-plans/"><img alt="Repo views" src="https://hits.sh/github.com/MaxInGaussian/pi-plans.svg?label=repo%20views" /></a>
|
|
16
|
-
<a href="
|
|
17
|
-
<img alt="
|
|
18
|
-
<img alt="
|
|
16
|
+
<a href="https://www.npmjs.com/package/pi-plans"><img alt="npm downloads" src="https://img.shields.io/npm/dt/pi-plans?color=38bdf8" /></a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/pi-plans"><img alt="npm version" src="https://img.shields.io/npm/v/pi-plans?color=60a5fa" /></a>
|
|
18
|
+
<a href="https://github.com/earendil-works/pi"><img alt="Pi package" src="https://img.shields.io/badge/Pi-package-fbbf24" /></a>
|
|
19
|
+
<a href="https://github.com/earendil-works/pi"><img alt="Pi coding agent" src="https://img.shields.io/badge/Pi%20coding%20agent-earendil--works-22d3ee" /></a>
|
|
20
|
+
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/pi-plans?color=22c55e" /></a>
|
|
21
|
+
<a href="https://github.com/MaxInGaussian/pi-plans/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/MaxInGaussian/pi-plans/actions/workflows/ci.yml/badge.svg" /></a>
|
|
19
22
|
</p>
|
|
20
23
|
|
|
21
24
|
---
|
|
@@ -60,7 +63,7 @@ A rough change request becomes a versioned Markdown plan instead of a surprise d
|
|
|
60
63
|
tracked execution loop
|
|
61
64
|
fused AGENTS.md × Ponytail executor rules
|
|
62
65
|
checklist injected each turn, [DONE:VC-xxx]
|
|
63
|
-
markers tracked via bottom status bar
|
|
66
|
+
markers tracked via bottom status bar
|
|
64
67
|
|
|
|
65
68
|
v
|
|
66
69
|
run status: done
|
|
@@ -110,10 +113,15 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
|
|
|
110
113
|
| Capability | In short |
|
|
111
114
|
|---|---|
|
|
112
115
|
| Planning router + five specialist skills | Start with `/skill:planning` to route to the narrowest matching specialist (`plan-small` → `plan-big`, `debug-and-plan`, `plan-with-refs`) |
|
|
113
|
-
| Choice prompts | `ask_choice`: recommended option first, answers auto-recorded per run |
|
|
114
|
-
| Refinement rounds | Read-only reviewer/criticizer Pi subagents consolidate findings into the next plan version |
|
|
116
|
+
| Choice prompts | `ask_choice`: recommended option first, answers auto-recorded per run; choosing Auto-complete enables recommendation-only answers for later eligible questions in the current planning run, with `/plans-autocomplete-stop` available to take back control |
|
|
117
|
+
| Refinement rounds | Read-only reviewer/criticizer Pi subagents consolidate findings into the next plan version; delegated runs have standalone `Reviewer`/`Criticizer` progress overlays that close before the tool result returns |
|
|
115
118
|
| Workspace state | Config, runs, decisions, refs, and subagent ledgers in `.git/pi_plans/` (git common dir) |
|
|
116
|
-
|
|
|
119
|
+
| Smart compact (I-aware) | History is sliced by `I-###` instead of VC. The current-I slice above 20% of the model window triggers a bounded summary with paired `Read:` records, retains a legal recent suffix, targets <10% post-context, and records a hard-floor reason when unreachable. Planning phase falls back to the latest plan/Q&A focus when no current marker exists; cooldown + resume guard prevent ping-pong; one hidden continuation is queued when Pi reports `willRetry: false` |
|
|
120
|
+
| Visible Refiner overlay | Delegated reviewer/criticizer subagents surface as a named public overlay in the TUI — one `Reviewer`/`Criticizer` panel with per-lane tool progress, bounded output preview, and clean cancelled/timed-out vs completed states. The overlay opens when the round starts and closes before the tool result returns to the main session |
|
|
121
|
+
| Tracked execution | Checklist injected each turn; `[DONE:VC-xxx]` markers drive completion; implementation items report progress with `[I-xxx:implemented]` / `[I-xxx:validating]` markers; the bottom status bar shows lifecycle, `x/y` progress, elapsed time, and input/output token usage in real time |
|
|
122
|
+
| Execution handoff | The accepted plan resumes in the current session model; no separate model selection is performed. |
|
|
123
|
+
| Execution-phase compaction | Pi core owns threshold, overflow, and manual scheduling; pi-plans adds a current-I proactive check once the current-I slice exceeds 20% of the model window, summarizes I-level history and bounded `Read:` records, retains a legal recent suffix, targets under 10% when possible, and records hard-floor reasons when not; one hidden continuation is queued when Pi reports `willRetry: false` |
|
|
124
|
+
| Planning-phase auto compaction | In active planning runs (run.status=planning, no execution), the current-I check uses the same 20%/10% best-effort policy when a marker exists; without a marker it protects the latest plan/Q&A focus, while Pi threshold, overflow, and manual compaction remain supported; cooldown + resume guard prevent ping-pong and hidden resume messages stay out of model context |
|
|
117
125
|
| Efficient executor prompt | Each turn, the executor is steered by a fused rule set — Marcos Hernanz's AGENTS.md principles × Ponytail minimalism: layered growth, simplest implementation, long-term architecture (no stopgaps), library discipline — so plans finish in fewer tokens and fewer detours |
|
|
118
126
|
| Write guard | `edit`/`write` blocked outside planning artifacts while a run is active |
|
|
119
127
|
|
|
@@ -123,14 +131,35 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
|
|
|
123
131
|
|---|---|
|
|
124
132
|
| `plans` | State CLI: `init`, `show`, `set-language`, `set-artifact-root`, `set-role`, `start-run`, `set-status`, `record-decision`, `record-ref`, `record-subagent` |
|
|
125
133
|
| `ask_choice` | Numbered choice prompt; `autoComplete: false` for the merged accept/execute question and external-state questions |
|
|
126
|
-
| `refine` | Reviewer/criticizer round via read-only subagents (`--tools read,grep,find,ls`); `reviewers: 3` for big plans; enforces role/model confirmation gates |
|
|
127
|
-
| `execute_plan` | Execution handoff: re-confirms with the user
|
|
128
|
-
| `/plans` | Show config, active run, execution progress |
|
|
134
|
+
| `refine` | Reviewer/criticizer round via standalone read-only subagents (`--mode json -p --no-session --tools read,grep,find,ls`); delegated TUI runs show one `Reviewer`/`Criticizer` overlay and close it before returning; `reviewers: 3` for big plans; enforces role/model confirmation gates |
|
|
135
|
+
| `execute_plan` | Execution handoff: re-confirms with the user and enters extension-managed execution mode |
|
|
136
|
+
| `/plans` | Show config, active run, and execution progress |
|
|
129
137
|
| `/plans-execute [plan.md]` | Manual execution handoff (defaults to highest `PLAN_vN.md`) |
|
|
130
138
|
| `/update-plan [plan.md] [reason…]` | Interrupt-and-refine: stops execution (if any), returns the run to planning, and directs the agent to revise the plan into `PLAN_vN+1.md` while preserving verified work |
|
|
139
|
+
| `/plans-autocomplete-stop` | Stop the current run's Auto-complete mode and return later planning questions to normal interaction |
|
|
131
140
|
| `/plans-stop` | Stop execution mode |
|
|
132
141
|
| `/plans-abandon` | Abandon the active run (lifts the write guard; artifacts stay) |
|
|
133
|
-
| Status bar (lifecycle) | 💬 Q&A → 📝 draft written (planning sub-phases) → ⌛ executing `x/y · spent · in/out-toks` → ⛔ stopped / 🎯 done / 🚫 abandoned |
|
|
142
|
+
| Status bar (lifecycle) | 💬 Q&A → 📝 draft written (planning sub-phases) → ⌛ executing `x/y · spent · in/out-toks` in the bottom status bar → ⛔ stopped / 🎯 done / 🚫 abandoned |
|
|
143
|
+
|
|
144
|
+
## Smart compact (I-aware)
|
|
145
|
+
|
|
146
|
+
Default `compaction` is Pi-core-owned: threshold, overflow, and manual `/compact` always run as designed. On top of that, pi-plans layers an **I-aware policy** so long, tool-heavy sessions survive the same run instead of running out of context:
|
|
147
|
+
|
|
148
|
+
- **Slice by implementation item.** History is grouped by `[I-###:current]` markers instead of `[DONE:VC-xxx]`. The current I's prefix can be summarized, the current I's recent suffix stays raw, and finished `I-###` items become independent sections.
|
|
149
|
+
- **Bounded read history.** Every paired `read` call/result is reduced to `Read: <path> line <X-Y> Extracted information summary: ...` (line range is `unknown` when no offset/limit is given). Records deduplicate by path/range across compactions and never embed full raw tool output.
|
|
150
|
+
- **20%/10% best-effort budget.** When the current-I slice exceeds 20% of `ctx.getContextUsage().contextWindow`, a compact is requested on the next settled turn. The summary's `details` record `contextWindow`, `tokensBefore`, `currentITokens`, `summaryTokens`, `keptSuffixTokens`, `estimatedAfterTokens`, `targetRatio`, `currentI`, `firstKeptEntryId`, `targetMet`, and a `hardFloorReason` when the 10% target cannot be reached (system prompt, tool definitions, single oversized tool result). Hard floors stop the loop; they do not silently fall through.
|
|
151
|
+
- **Pi-owned scheduling preserved.** Threshold, overflow, and manual triggers still come from Pi core. pi-plans only customizes the summary and re-arms once usage falls below the low watermark. The hidden `Continue execution.` resume message is queued on non-retry compactions and never enters model context.
|
|
152
|
+
- **Bounded model call.** Custom summaries reuse the current Pi model via `ctx.modelRegistry.complete(model, context, options)` with `event.signal`, `cacheRetention: "none"`, a fresh `sessionId`, and bounded `maxTokens`. Empty, length-stopped, error, or tool-call responses fall back to Pi's default compaction — no half-checkpoint is ever written.
|
|
153
|
+
- **Phase isolation.** Planning and execution keep independent compaction state (`pi-plans-plan-resume` vs `pi-plans-exec-resume`); planning without a current marker protects the latest plan/Q&A focus rather than leaking execution state.
|
|
154
|
+
|
|
155
|
+
## Visible Refiner overlay
|
|
156
|
+
|
|
157
|
+
Delegated `refine` rounds (reviewer or criticizer) show their progress directly inside the Pi TUI instead of disappearing into the child process's terminal. The overlay is a public, named panel so users always know who is doing what:
|
|
158
|
+
|
|
159
|
+
- **Named public overlays.** Each round uses the literal overlay name `Reviewer` or `Criticizer` (no dependency on `pi-btw`; the renderer is built on Pi's public `pi-tui` primitives). The big-plan concurrent reviewer round renders one reviewer lane per subagent under the same `Reviewer` overlay.
|
|
160
|
+
- **Bounded live detail.** The overlay tracks lane state (`pending → running → completed | cancelled | timed-out`) and the most recent tool call plus a clipped argument preview. Raw tool output is never surfaced, so progress stays legible even when subagents read large files.
|
|
161
|
+
- **Clean lifecycle edges.** The overlay opens at round start, advances via the JSONL progress feed emitted by `pi --mode json`, and is `close()`d before the round's conclusion returns as a tool result to the main session. Cancelled and timed-out children render as terminal states with the original error message — never as silent drops.
|
|
162
|
+
- **Tool-only progress.** The overlay only consumes tool and message lifecycle events from the child; unrelated `pi` events are ignored, so a noisy upstream release does not desync the panel.
|
|
134
163
|
|
|
135
164
|
## The execution rules
|
|
136
165
|
|
|
@@ -148,6 +177,9 @@ Once you approve the handoff, every turn injects a compact rule set that fuses M
|
|
|
148
177
|
|
|
149
178
|
The rules cost four lines per turn and buy back far more: fewer wrong turns, shorter implementation paths, plans that finish in fewer tokens.
|
|
150
179
|
|
|
180
|
+
Waiting for subprocess-backed verification:
|
|
181
|
+
For subprocess-backed verification, when a step starts a subprocess and needs its result before verifying, use literal `waiting for` with backoff `5s -> 10s -> 20s -> 40s -> 80s`, then keep polling at 80s; restart at 5s for each new subprocess.
|
|
182
|
+
|
|
151
183
|
## Skills
|
|
152
184
|
|
|
153
185
|
Invoked via `resources_discover`, callable as `/skill:<name>`, directly as `/<name>` (e.g. `/planning`, `/plan-small` — extension aliases that forward to the skill), or picked automatically from the task description.
|
|
@@ -188,12 +220,12 @@ or register the absolute path in `~/.pi/agent/settings.json`:
|
|
|
188
220
|
pi-plans/
|
|
189
221
|
├── index.ts # Extension entry: tools, commands, guard, execution loop
|
|
190
222
|
├── tools/ # plans, ask-choice, refine, execute-plan
|
|
191
|
-
├── src/ # state, guard, plan parsing, subagent runner, exec loop
|
|
223
|
+
├── src/ # state, guard, plan parsing, subagent runner, refine overlay, exec loop
|
|
192
224
|
├── skills/ # The planning router plus five specialist planning skills
|
|
193
225
|
├── references/ # Shared workflow, state/config, plan template (normative)
|
|
194
226
|
├── agents/ # reviewer.md / criticizer.md subagent prompts
|
|
195
227
|
├── scripts/validate.ts # Structure validator
|
|
196
|
-
└── tests/ # node:test suite (state, guard, plan parsing, execution)
|
|
228
|
+
└── tests/ # node:test suite (state, guard, plan parsing, execution, refine progress)
|
|
197
229
|
```
|
|
198
230
|
|
|
199
231
|
## Safety model
|
|
@@ -217,7 +249,7 @@ The plan is the contract. Refinement converges on scope while nothing is writabl
|
|
|
217
249
|
|
|
218
250
|
**What can Auto-complete decide on my behalf?**
|
|
219
251
|
|
|
220
|
-
Planning and refinement choices only (the recommended option). It is never offered for execution approval, installs, publishing, deployment, merge, push, or credentials — those questions stop and wait for you.
|
|
252
|
+
Planning and refinement choices only (the recommended option). Choosing Auto-complete enables the recommended answer for later eligible planning questions in the current run and the extension continues the planning turn when the model stops early. Use `/plans-autocomplete-stop` to take back control. It is never offered for execution approval, installs, publishing, deployment, merge, push, or credentials — those questions stop and wait for you.
|
|
221
253
|
|
|
222
254
|
**Where does all the state live?**
|
|
223
255
|
|
package/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - `refine` tool — reviewer/criticizer rounds via read-only pi subagents
|
|
11
11
|
* - `execute_plan` — execution handoff into the tracked execution loop
|
|
12
12
|
* - write guard — planning runs may only write planning artifacts
|
|
13
|
-
* - execution loop — checklist injection, [DONE:VC-xxx] tracking, progress
|
|
13
|
+
* - execution loop — checklist injection, [DONE:VC-xxx] tracking, progress status
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -19,21 +19,37 @@ import * as path from "node:path";
|
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
consumePendingPanelSync,
|
|
22
|
+
consumePlanningCompactionResumeGuard,
|
|
23
|
+
drainExecutionFlush,
|
|
25
24
|
executionContextMessage,
|
|
25
|
+
filterExecutionResumeMessages,
|
|
26
|
+
filterPlanningResumeMessages,
|
|
26
27
|
getExecution,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
handleExecutionBeforeCompact,
|
|
29
|
+
handleExecutionCompact,
|
|
30
|
+
handleExecutionCompactFailed,
|
|
31
|
+
handleExecutionTurnCompaction,
|
|
32
|
+
handlePlanningBeforeCompact,
|
|
33
|
+
handlePlanningCompact,
|
|
34
|
+
handlePlanningCompactFailed,
|
|
35
|
+
PLANNING_PLAN_WRITTEN_CUSTOM_TYPE,
|
|
36
|
+
registerExecutionTurnHandlers,
|
|
37
|
+
refreshPlanningCompactionCooldown,
|
|
38
|
+
requestPlanningCompaction,
|
|
30
39
|
restoreFromSession,
|
|
31
40
|
stopExecution,
|
|
32
|
-
syncExecutionPanel,
|
|
33
|
-
toggleExecutionPanelView,
|
|
34
41
|
updateStatusWidget,
|
|
42
|
+
shouldTriggerPlanningCompaction,
|
|
35
43
|
} from "./src/exec.ts";
|
|
44
|
+
import {
|
|
45
|
+
autoCompleteStatus,
|
|
46
|
+
disableAutoComplete,
|
|
47
|
+
markPlanWritten,
|
|
48
|
+
registerAutoCompleteTurnHandlers,
|
|
49
|
+
restoreAutoCompleteFromSession,
|
|
50
|
+
} from "./src/autocomplete.ts";
|
|
36
51
|
import { planningWriteBlockReason } from "./src/guard.ts";
|
|
52
|
+
import { registerQueryInterviewHooks } from "./src/query-hook.ts";
|
|
37
53
|
import { latestPlanVersion, nextPlanVersionPath } from "./src/plan.ts";
|
|
38
54
|
import { getRun, readActive, recordDecision, resolveStateRootOrNull, setRunStatus } from "./src/state.ts";
|
|
39
55
|
import { registerAskChoiceTool } from "./tools/ask-choice.ts";
|
|
@@ -43,20 +59,37 @@ import { registerRefineTool } from "./tools/refine.ts";
|
|
|
43
59
|
|
|
44
60
|
const baseDir = dirname(fileURLToPath(import.meta.url));
|
|
45
61
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
// When THIS copy of the extension was imported into the running pi process.
|
|
63
|
+
// /plans compares it against the newest source-file mtime so a stale instance
|
|
64
|
+
// (code on disk newer than the loaded copy) is immediately visible.
|
|
65
|
+
const extensionLoadedAt = new Date();
|
|
66
|
+
|
|
67
|
+
function extensionStalenessLine(): string {
|
|
68
|
+
try {
|
|
69
|
+
const dirs = [baseDir, path.join(baseDir, "src"), path.join(baseDir, "tools")];
|
|
70
|
+
let newest = 0;
|
|
71
|
+
for (const dir of dirs) {
|
|
72
|
+
for (const name of fs.readdirSync(dir)) {
|
|
73
|
+
if (!name.endsWith(".ts")) continue;
|
|
74
|
+
const mtime = fs.statSync(path.join(dir, name)).mtimeMs;
|
|
75
|
+
if (mtime > newest) newest = mtime;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (newest > extensionLoadedAt.getTime() + 2000) {
|
|
79
|
+
return `⚠ extension code on disk is newer than the loaded copy (loaded ${extensionLoadedAt.toISOString()}); run /reload to pick it up`;
|
|
80
|
+
}
|
|
81
|
+
return `Extension loaded: ${extensionLoadedAt.toISOString()} (up to date)`;
|
|
82
|
+
} catch {
|
|
83
|
+
return `Extension loaded: ${extensionLoadedAt.toISOString()}`;
|
|
58
84
|
}
|
|
59
|
-
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hasActivePlanningWorkflow(ctx: Parameters<typeof updateStatusWidget>[0]): boolean {
|
|
88
|
+
if (getExecution()) return true;
|
|
89
|
+
const active = readActive(ctx.cwd);
|
|
90
|
+
if (!active) return false;
|
|
91
|
+
const status = getRun(ctx.cwd, active.run_id)?.status;
|
|
92
|
+
return status === "planning" || status === "accepted" || status === "executing";
|
|
60
93
|
}
|
|
61
94
|
|
|
62
95
|
export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
@@ -64,6 +97,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
64
97
|
registerAskChoiceTool(pi);
|
|
65
98
|
registerRefineTool(pi, baseDir);
|
|
66
99
|
registerExecutePlanTool(pi);
|
|
100
|
+
registerQueryInterviewHooks(pi, hasActivePlanningWorkflow);
|
|
67
101
|
|
|
68
102
|
// Contribute the router skill plus the five specialist planning skills.
|
|
69
103
|
pi.on("resources_discover", () => ({
|
|
@@ -105,31 +139,71 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
105
139
|
// not been approved), edit/write may only target planning artifacts.
|
|
106
140
|
// -----------------------------------------------------------------------
|
|
107
141
|
pi.on("tool_call", async (event, ctx) => {
|
|
108
|
-
|
|
109
|
-
if (execution) {
|
|
110
|
-
if (event.toolName === "edit" || event.toolName === "write") {
|
|
111
|
-
const rawPath = String((event.input as { path?: string }).path ?? "");
|
|
112
|
-
if (rawPath) recordTouchedPaths(ctx.cwd, [rawPath]);
|
|
113
|
-
}
|
|
114
|
-
if (event.toolName === "bash") {
|
|
115
|
-
const command = String((event.input as { command?: string }).command ?? "");
|
|
116
|
-
if (command) recordTouchedPaths(ctx.cwd, extractPathsFromBash(command));
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
142
|
+
if (getExecution()) return;
|
|
120
143
|
const rawPath = String((event.input as { path?: string }).path ?? "");
|
|
121
144
|
if (!rawPath) return;
|
|
122
145
|
const reason = planningWriteBlockReason({ workdir: ctx.cwd, toolName: event.toolName, rawPath });
|
|
123
|
-
if (
|
|
124
|
-
|
|
146
|
+
if (reason) return { block: true, reason };
|
|
147
|
+
// Allowed write: if it lands exactly on the run's latest plan file, drop a
|
|
148
|
+
// marker entry so planning-phase compaction can anchor its cut point there.
|
|
149
|
+
if (event.toolName === "write" || event.toolName === "edit") {
|
|
150
|
+
const active = readActive(ctx.cwd);
|
|
151
|
+
if (active) {
|
|
152
|
+
const latest = latestPlanVersion(active.artifact_dir);
|
|
153
|
+
if (latest && path.resolve(ctx.cwd, rawPath) === path.resolve(ctx.cwd, latest.path)) {
|
|
154
|
+
pi.appendEntry(PLANNING_PLAN_WRITTEN_CUSTOM_TYPE, {
|
|
155
|
+
runId: active.run_id,
|
|
156
|
+
planPath: latest.path,
|
|
157
|
+
});
|
|
158
|
+
markPlanWritten(ctx);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
pi.on("context", (event) => {
|
|
166
|
+
const filteredExecution = filterExecutionResumeMessages(event.messages as Array<{ customType?: string }>);
|
|
167
|
+
const messages = filterPlanningResumeMessages(filteredExecution);
|
|
168
|
+
if (messages.length !== event.messages.length) {
|
|
169
|
+
return { messages };
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
174
|
+
const executionResult = await handleExecutionBeforeCompact(pi, ctx, event);
|
|
175
|
+
if (executionResult) return executionResult;
|
|
176
|
+
return handlePlanningBeforeCompact(pi, ctx, event);
|
|
177
|
+
});
|
|
178
|
+
pi.on("session_compact", async (event, ctx) => {
|
|
179
|
+
handleExecutionCompact(pi, ctx, event);
|
|
180
|
+
handlePlanningCompact(pi, ctx, event);
|
|
181
|
+
});
|
|
182
|
+
pi.on("session_compact_failed", async (event, ctx) => {
|
|
183
|
+
handleExecutionCompactFailed(pi, ctx, event);
|
|
184
|
+
handlePlanningCompactFailed(pi, ctx, event);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Flush points for deferred execution-loop writes: primary drain when the
|
|
188
|
+
// agent run fully settles, backstop drain at the next run's start (covers
|
|
189
|
+
// continuation paths that might not emit agent_settled), plus the forced
|
|
190
|
+
// synchronous flush inside stop/complete.
|
|
191
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
192
|
+
drainExecutionFlush(pi, ctx);
|
|
125
193
|
});
|
|
126
194
|
|
|
127
195
|
// -----------------------------------------------------------------------
|
|
128
196
|
// Execution loop: inject remaining checklist each turn, track markers.
|
|
129
197
|
// -----------------------------------------------------------------------
|
|
130
|
-
pi.on("before_agent_start", async () => {
|
|
198
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
199
|
+
drainExecutionFlush(pi, ctx);
|
|
131
200
|
const content = executionContextMessage();
|
|
132
|
-
if (!content)
|
|
201
|
+
if (!content) {
|
|
202
|
+
if (!getExecution() && shouldTriggerPlanningCompaction(ctx)) {
|
|
203
|
+
requestPlanningCompaction(ctx);
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
133
207
|
return {
|
|
134
208
|
message: {
|
|
135
209
|
customType: "pi-plans-exec-context",
|
|
@@ -139,58 +213,27 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
139
213
|
};
|
|
140
214
|
});
|
|
141
215
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (!message || message.role !== "assistant") {
|
|
155
|
-
updateStatusWidget(ctx);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const text = (message.content ?? [])
|
|
159
|
-
.filter((part) => part.type === "text")
|
|
160
|
-
.map((part) => part.text ?? "")
|
|
161
|
-
.join("\n");
|
|
162
|
-
const changedIds = applyDoneMarkers(text);
|
|
163
|
-
if (changedIds.length > 0) {
|
|
164
|
-
// Attribute this turn's token usage to the finished items.
|
|
165
|
-
const projection = (event.message as { usage?: { input?: number; output?: number } }).usage;
|
|
166
|
-
const raw = projection ?? lastAssistantUsage;
|
|
167
|
-
lastAssistantUsage = null; // consumed: never re-attribute a stale turn
|
|
168
|
-
const usage = raw ? { input: raw.input ?? 0, output: raw.output ?? 0 } : undefined;
|
|
169
|
-
recordExecutionCompletion(pi, ctx, changedIds, usage);
|
|
170
|
-
}
|
|
171
|
-
if (getExecution() && isExecutionComplete()) {
|
|
172
|
-
completeExecution(pi, ctx);
|
|
216
|
+
registerExecutionTurnHandlers(pi, async (ctx) => {
|
|
217
|
+
if (getExecution()) {
|
|
218
|
+
handleExecutionTurnCompaction(ctx);
|
|
219
|
+
} else {
|
|
220
|
+
refreshPlanningCompactionCooldown(ctx);
|
|
221
|
+
if (consumePlanningCompactionResumeGuard(ctx)) {
|
|
222
|
+
updateStatusWidget(ctx);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (shouldTriggerPlanningCompaction(ctx)) {
|
|
226
|
+
requestPlanningCompaction(ctx);
|
|
227
|
+
}
|
|
173
228
|
}
|
|
174
|
-
// A
|
|
175
|
-
// turn just ended, so this is the safe point to apply it.
|
|
176
|
-
if (consumePendingPanelSync()) syncExecutionPanel(ctx);
|
|
229
|
+
// A completed turn is the safe point for status updates.
|
|
177
230
|
updateStatusWidget(ctx);
|
|
178
231
|
});
|
|
232
|
+
registerAutoCompleteTurnHandlers(pi);
|
|
179
233
|
|
|
180
234
|
// -----------------------------------------------------------------------
|
|
181
|
-
// Commands
|
|
235
|
+
// Commands
|
|
182
236
|
// -----------------------------------------------------------------------
|
|
183
|
-
pi.registerShortcut("alt+o", {
|
|
184
|
-
description: "Toggle pi-plans execution checklist widget",
|
|
185
|
-
handler: async (ctx) => {
|
|
186
|
-
const expanded = toggleExecutionPanelView(pi, ctx);
|
|
187
|
-
if (expanded === null) {
|
|
188
|
-
ctx.ui.notify("No execution in progress.", "info");
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
ctx.ui.notify(expanded ? "Execution checklist expanded." : "Execution checklist collapsed.", "info");
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
237
|
|
|
195
238
|
pi.registerCommand("plans", {
|
|
196
239
|
description: "Show pi-plans state: config, active run, and execution progress",
|
|
@@ -215,19 +258,17 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
215
258
|
lines.push(` ${item.done ? "☑" : "☐"} ${item.id}`);
|
|
216
259
|
}
|
|
217
260
|
}
|
|
261
|
+
lines.push(`Auto-complete: ${autoCompleteStatus(ctx)}`);
|
|
262
|
+
lines.push(extensionStalenessLine());
|
|
218
263
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
219
264
|
},
|
|
220
265
|
});
|
|
221
266
|
|
|
222
|
-
pi.registerCommand("plans-
|
|
223
|
-
description: "
|
|
267
|
+
pi.registerCommand("plans-autocomplete-stop", {
|
|
268
|
+
description: "Stop Auto-complete for the active planning run",
|
|
224
269
|
handler: async (_args, ctx) => {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
ctx.ui.notify("No execution in progress.", "info");
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
ctx.ui.notify(expanded ? "Execution checklist expanded." : "Execution checklist collapsed.", "info");
|
|
270
|
+
const stopped = disableAutoComplete(ctx, "stopped by user");
|
|
271
|
+
ctx.ui.notify(stopped ? "Auto-complete stopped." : "Auto-complete is not active.", stopped ? "info" : "warning");
|
|
231
272
|
},
|
|
232
273
|
});
|
|
233
274
|
|
|
@@ -264,6 +305,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
264
305
|
|
|
265
306
|
const active = readActive(ctx.cwd);
|
|
266
307
|
const execution = getExecution();
|
|
308
|
+
disableAutoComplete(ctx, "plan update");
|
|
267
309
|
|
|
268
310
|
// Resolve the plan to revise: explicit arg > running execution > latest in artifact dir.
|
|
269
311
|
let sourcePlanPath: string | null = planArg
|
|
@@ -292,7 +334,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
292
334
|
`${doneIds.length}/${execution.items.length} verifier item(s) already verified; their work stays. Remaining items return to planning.`,
|
|
293
335
|
);
|
|
294
336
|
if (!ok) return;
|
|
295
|
-
stopExecution(pi, ctx, "interrupted by /update-plan");
|
|
337
|
+
await stopExecution(pi, ctx, "interrupted by /update-plan");
|
|
296
338
|
}
|
|
297
339
|
|
|
298
340
|
// Return the run to planning so refinement rules and guards apply again.
|
|
@@ -353,7 +395,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
353
395
|
}
|
|
354
396
|
const ok = await ctx.ui.confirm("Stop execution?", "Remaining verifier items will be left unfinished.");
|
|
355
397
|
if (!ok) return;
|
|
356
|
-
stopExecution(pi, ctx, "stopped by user via /plans-stop");
|
|
398
|
+
await stopExecution(pi, ctx, "stopped by user via /plans-stop");
|
|
357
399
|
ctx.ui.notify("Execution stopped.", "info");
|
|
358
400
|
},
|
|
359
401
|
});
|
|
@@ -371,6 +413,11 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
371
413
|
`${active.run_id}\nThe read-only guard lifts; committed artifacts stay in place.`,
|
|
372
414
|
);
|
|
373
415
|
if (!ok) return;
|
|
416
|
+
// Abandon must end execution first so the planning model is restored.
|
|
417
|
+
disableAutoComplete(ctx, "run abandoned");
|
|
418
|
+
if (getExecution()) {
|
|
419
|
+
await stopExecution(pi, ctx, "run abandoned via /plans-abandon");
|
|
420
|
+
}
|
|
374
421
|
try {
|
|
375
422
|
setRunStatus(ctx.cwd, active.run_id, "abandoned");
|
|
376
423
|
ctx.ui.notify(`Run ${active.run_id} abandoned.`, "info");
|
|
@@ -385,6 +432,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
385
432
|
// Session lifecycle
|
|
386
433
|
// -----------------------------------------------------------------------
|
|
387
434
|
pi.on("session_start", async (_event, ctx) => {
|
|
388
|
-
restoreFromSession(pi, ctx, ctx.sessionManager.getEntries() as unknown as Parameters<typeof restoreFromSession>[2]);
|
|
435
|
+
await restoreFromSession(pi, ctx, ctx.sessionManager.getEntries() as unknown as Parameters<typeof restoreFromSession>[2]);
|
|
436
|
+
restoreAutoCompleteFromSession(ctx, ctx.sessionManager.getEntries() as unknown as Parameters<typeof restoreAutoCompleteFromSession>[1]);
|
|
389
437
|
});
|
|
390
438
|
}
|
package/package.json
CHANGED
|
@@ -9,8 +9,9 @@ This skill set is written for the Pi coding agent's documented behavior:
|
|
|
9
9
|
- the five skills are contributed by the pi-plans extension and loaded as Pi skills (also invokable as `/skill:<name>`);
|
|
10
10
|
- skill references and helper sources are resolved relative to the directory containing `SKILL.md`;
|
|
11
11
|
- the extension provides these tools: `plans` (workspace state), `ask_choice` (choice prompts), `refine` (reviewer/criticizer subagents), and `execute_plan` (execution handoff);
|
|
12
|
-
- `refine` spawns read-only Pi subagents (`pi --mode json -p --no-session --tools read,grep,find,ls`) with isolated context;
|
|
13
|
-
- the execution loop is extension-managed: remaining verifier items are injected each turn and `[DONE:VC-xxx]` markers are tracked with a
|
|
12
|
+
- `refine` spawns read-only Pi subagents (`pi --mode json -p --no-session --tools read,grep,find,ls`) with isolated context; delegated Reviewer/Criticizer runs show a standalone aggregate overlay titled `Reviewer` or `Criticizer`, close the overlay before returning, and return conclusions to the main session as tool output;
|
|
13
|
+
- the execution loop is extension-managed: remaining verifier items are injected each turn, implementation items emit `[I-###:current]`/`[I-###:implemented|validating]`, and `[DONE:VC-xxx]` markers are tracked with a bottom status bar;
|
|
14
|
+
- execution and planning compaction keep Pi's SessionManager as the history owner, group compacted history by implementation item, retain a legal current-I suffix, summarize paired reads as bounded `Read: <path> line <X-Y> Extracted information summary: ...` records, and target `<10%` of the model window after a current-I slice exceeds `20%`; hard-floor cases are recorded instead of looping;
|
|
14
15
|
|
|
15
16
|
## Planning Boundary
|
|
16
17
|
|
|
@@ -60,6 +61,7 @@ Every user-facing planning or refinement question goes through the `ask_choice`
|
|
|
60
61
|
- `options`: ordered options, recommended option first with `recommended: true` (exactly one), each with the tradeoff that matters in `description`;
|
|
61
62
|
- do not add `Other` or `Auto-complete` yourself — the tool appends `Other…` second-last and `Auto-complete` last;
|
|
62
63
|
- pass `autoComplete: false` for the merged accept/execute question — it contains the execution approval, so Auto-complete never appears there — and for any install waiver, publishing, deployment, merge, push, credential, or external-state question. Auto-complete may choose the recommended planning or refinement option only.
|
|
64
|
+
- When the user selects Auto-complete, it remains active for the current planning run: later eligible questions use their recommended options automatically, and the extension queues one deduplicated follow-up if the model stops after an auto-completed answer. `/plans-autocomplete-stop` disables it; session restore may reactivate it only for the same active run while its status is `planning`.
|
|
63
65
|
|
|
64
66
|
Answers are recorded automatically in the active run's `decisions.jsonl`. You must still maintain `DECISIONS.md` in the artifact directory (summary table of questions, options, answers, answer sources, open assumptions).
|
|
65
67
|
|
|
@@ -126,7 +128,8 @@ A refinement round is complete when all reviewer outputs have returned or all cr
|
|
|
126
128
|
|
|
127
129
|
When the user picks `✓ Accept PLAN_vN and execute it now` in the merged question, mark the plan accepted and call the `execute_plan` tool (or the user runs `/plans-execute`). It re-confirms with the user, then the extension enters execution mode:
|
|
128
130
|
|
|
129
|
-
- every agent turn is injected with the remaining verifier checklist and execution rules (layered simplest implementation, no stopgaps, dependency and library discipline, minimum tests);
|
|
131
|
+
- every agent turn is injected with the remaining verifier checklist and execution rules (layered simplest implementation, waiting for subprocess-backed verification with backoff 5s -> 10s -> 20s -> 40s -> 80s, then keep polling at 80s and restart at 5s for each new subprocess, no stopgaps, dependency and library discipline, minimum tests);
|
|
132
|
+
- execution-phase compaction may proactively compact the current-I slice above 20% of the model window, summarizes the oldest eligible prefix and bounded Read records, preserves a legal recent suffix, and records whether the <10% target was met; Pi still owns threshold, overflow, and manual scheduling;
|
|
130
133
|
- the read-only guard lifts: full write access returns;
|
|
131
134
|
- the run status moves to `executing`, then `done` when the last `[DONE:VC-xxx]` marker lands;
|
|
132
135
|
- `/plans-stop` stops execution; `/plans` shows progress.
|
|
@@ -120,7 +120,7 @@ If a spawn later fails because the stored selector is unavailable, reset the mar
|
|
|
120
120
|
|
|
121
121
|
## Subagent Spawning
|
|
122
122
|
|
|
123
|
-
When `mode` is `delegated-subagent`, the `refine` tool spawns a read-only `pi` subprocess (`--mode json -p --no-session --tools read,grep,find,ls`) whose system prompt comes from `agents/reviewer.md` or `agents/criticizer.md`. The subagent:
|
|
123
|
+
When `mode` is `delegated-subagent`, the `refine` tool spawns a read-only `pi` subprocess (`--mode json -p --no-session --tools read,grep,find,ls`) whose system prompt comes from `agents/reviewer.md` or `agents/criticizer.md`. In TUI mode, delegated runs also show a standalone `Reviewer` or `Criticizer` overlay with live lane/tool status; the child is awaited and the overlay is closed before the tool result returns. The subagent:
|
|
124
124
|
|
|
125
125
|
- performs read-only analysis and never edits files;
|
|
126
126
|
- receives the full plan text and a review/criticism brief;
|