pi-plans 0.2.0 → 0.3.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.
Files changed (65) hide show
  1. package/README.md +74 -21
  2. package/index.ts +115 -9
  3. package/package.json +7 -1
  4. package/references/pi-planning-workflow.md +18 -3
  5. package/references/state-and-config.md +34 -2
  6. package/scripts/validate.ts +4 -0
  7. package/src/code-graph/commands.ts +437 -0
  8. package/src/code-graph/discovery.ts +118 -0
  9. package/src/code-graph/git.ts +108 -0
  10. package/src/code-graph/identity.ts +59 -0
  11. package/src/code-graph/indexer.ts +281 -0
  12. package/src/code-graph/materialize.ts +166 -0
  13. package/src/code-graph/mode.ts +28 -0
  14. package/src/code-graph/mutations.ts +160 -0
  15. package/src/code-graph/parser.ts +51 -0
  16. package/src/code-graph/parsers/javascript.ts +35 -0
  17. package/src/code-graph/parsers/python.ts +160 -0
  18. package/src/code-graph/parsers/tree-sitter.ts +316 -0
  19. package/src/code-graph/paths.ts +85 -0
  20. package/src/code-graph/prompts.ts +18 -0
  21. package/src/code-graph/resolver.ts +69 -0
  22. package/src/code-graph/runtime.ts +158 -0
  23. package/src/code-graph/schema.ts +135 -0
  24. package/src/code-graph/screening.ts +82 -0
  25. package/src/code-graph/store.ts +278 -0
  26. package/src/code-graph/summary.ts +435 -0
  27. package/src/code-graph/types.ts +163 -0
  28. package/src/compaction.ts +1125 -371
  29. package/src/config-command.ts +326 -0
  30. package/src/exec.ts +356 -686
  31. package/src/refine-prompts.ts +50 -0
  32. package/src/refine-ui-helpers.ts +71 -18
  33. package/src/refine-ui-state.ts +87 -21
  34. package/src/refine-ui.ts +210 -102
  35. package/src/state.ts +19 -6
  36. package/src/subagent.ts +163 -61
  37. package/tests/ask-choice.test.ts +263 -0
  38. package/tests/autocomplete.test.ts +6 -1
  39. package/tests/code-graph-apply.test.ts +185 -0
  40. package/tests/code-graph-commands.test.ts +211 -0
  41. package/tests/code-graph-db.test.ts +166 -0
  42. package/tests/code-graph-discovery.test.ts +38 -0
  43. package/tests/code-graph-git.test.ts +94 -0
  44. package/tests/code-graph-index.test.ts +175 -0
  45. package/tests/code-graph-loop.e2e.test.ts +159 -0
  46. package/tests/code-graph-mutations.test.ts +117 -0
  47. package/tests/code-graph-parser.test.ts +85 -0
  48. package/tests/code-graph-rollback.test.ts +100 -0
  49. package/tests/code-graph-summary-batching.test.ts +518 -0
  50. package/tests/code-graph-summary.test.ts +148 -0
  51. package/tests/compaction.test.ts +371 -57
  52. package/tests/config-command.test.ts +255 -0
  53. package/tests/exec.test.ts +665 -241
  54. package/tests/fixtures/code-graph/sample.js +36 -0
  55. package/tests/fixtures/code-graph/sample.py +20 -0
  56. package/tests/fixtures/code-graph/sample.ts +15 -0
  57. package/tests/graph-aware-file-tools.test.ts +411 -0
  58. package/tests/refine-prompts.test.ts +67 -2
  59. package/tests/refine-ui.test.ts +337 -72
  60. package/tests/subagent.test.ts +26 -20
  61. package/tools/ask-choice.ts +158 -11
  62. package/tools/code-graph.ts +254 -0
  63. package/tools/graph-aware-file-tools.ts +392 -0
  64. package/tools/plans.ts +84 -1
  65. package/tools/refine.ts +61 -15
package/README.md CHANGED
@@ -113,15 +113,15 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
113
113
  | Capability | In short |
114
114
  |---|---|
115
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`) |
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 |
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. After execution completes, the continuation prompt enters goal-running mode by default: it asks only for the implementation-review loop's termination condition and then keeps refining until the loop ends or the cap is reached. |
117
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 |
118
118
  | Workspace state | Config, runs, decisions, refs, and subagent ledgers in `.git/pi_plans/` (git common dir) |
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 |
119
+ | VCC compact | Active planning/execution compaction uses deterministic, no-LLM VCC-style summaries when Pi core emits manual `/compact`, threshold, or overflow events. Summaries use five bracket sections plus a brief transcript, keep a smart recent tail, support `keep:N`, and write VCC details/stats without adding `/pi-vcc` commands. |
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, full streaming transcript with follow-bottom scroll, Tab-pane focus, retention until the user presses `Esc` after completion, and clean cancelled/timed-out vs completed states. `reviewers: 3` renders three equal-height panes inside the same overlay |
121
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
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 |
123
+ | Execution-phase compaction | Pi core owns scheduling; pi-plans maps the active plan path, current `I-###`, implementation IDs, and remaining `VC-###` checklist into the VCC sections. The old current-I proactive trigger and model-generated summary path are removed. |
124
+ | Planning-phase compaction | During `run.status=planning` with no active execution, pi-plans maps active run, artifact directory, latest plan path from session entries, and observed current-I markers into the VCC sections. Without an active planning run, compaction returns to Pi core. |
125
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 |
126
126
  | Write guard | `edit`/`write` blocked outside planning artifacts while a run is active |
127
127
 
@@ -131,9 +131,10 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
131
131
  |---|---|
132
132
  | `plans` | State CLI: `init`, `show`, `set-language`, `set-artifact-root`, `set-role`, `start-run`, `set-status`, `record-decision`, `record-ref`, `record-subagent` |
133
133
  | `ask_choice` | Numbered choice prompt; `autoComplete: false` for the merged accept/execute question and external-state questions |
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 |
134
+ | `refine` | Reviewer/criticizer round via standalone read-only subagents (`--mode json -p --no-session --tools read,grep,find,ls`, plus `code_graph` for both roles when the workspace has the code graph enabled); `target: "plan"` (default) reviews the plan, `target: "implementation"` reviews the implemented worktree against the plan; delegated TUI runs show one `Reviewer`/`Criticizer` overlay (78% width × 78% height, top-center, ≥72 cols) with per-lane transcript, follow-bottom scroll, Tab focus, and retention until `Esc`; `reviewers: 3` renders three equal-height panes; enforces role/model confirmation gates |
135
135
  | `execute_plan` | Execution handoff: re-confirms with the user and enters extension-managed execution mode |
136
136
  | `/plans` | Show config, active run, and execution progress |
137
+ | `/config-pi-plans` | Re-ask workspace defaults for language, artifact root, code graph, reviewer mode/model, and criticizer mode/model |
137
138
  | `/plans-execute [plan.md]` | Manual execution handoff (defaults to highest `PLAN_vN.md`) |
138
139
  | `/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
140
  | `/plans-autocomplete-stop` | Stop the current run's Auto-complete mode and return later planning questions to normal interaction |
@@ -141,24 +142,26 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
141
142
  | `/plans-abandon` | Abandon the active run (lifts the write guard; artifacts stay) |
142
143
  | 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
 
144
- ## Smart compact (I-aware)
145
+ ## VCC compact
145
146
 
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
+ Pi core remains the owner of compaction scheduling: manual `/compact`, threshold, and overflow events are emitted by Pi as usual. During an active pi-plans planning or execution run, pi-plans handles `session_before_compact` with a deterministic VCC-style compiler instead of calling a model for a summary.
147
148
 
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.
149
+ - **Summary shape.** The summary contains exactly five bracket sections: `[Session Goal]`, `[Files And Changes]`, `[Commits]`, `[Outstanding Context]`, and `[User Preferences]`, followed by `---` and a ranked brief transcript. Execution contributes plan path, current `I-###`, implementation IDs, and remaining verifier IDs; planning contributes run ID, artifact directory, latest plan path from session entries, and any observed current-I marker.
150
+ - **Session-only input.** Compact summaries are built from the event's branch entries, previous summary, file ops, pi-plans custom session entries, and live phase state. The compiler does not read plan files, git history, or the worktree to invent context.
151
+ - **Tail policy.** The default keep is one recent user turn; smart keep may retain more turns when the tail is still small. Explicit `keep:N` is honored, while no-anchor and oversized-tail cases use a deterministic token-budget cut that avoids starting retained context with an orphan tool result.
152
+ - **Manual matrix.** Plain `/compact` and `/compact keep:N` compact and show stats without continuing. `/compact <text>` and `/compact keep:N <text>` compact, then send the text once as the follow-up prompt. Internal pi-plans compaction markers are never reused as user follow-up prompts.
153
+ - **Fallbacks and stats.** Unsafe manual/threshold cuts cancel with a warning; overflow or retrying unsafe cuts return control to Pi core. Successful VCC compactions notify with kept-tail and summarized-message stats. Threshold/overflow compactions may queue one hidden continuation only when the running Pi version still needs it and `continueAfterThresholdCompact` is enabled.
154
+ - **Repo-private config.** Defaults are scaffolded in `.git/pi_plans/pi-vcc-config.json` under the resolved git common dir: `overrideDefaultCompaction:true`, `smartKeepTail:true`, `continueAfterThresholdCompact:true`, `debug:false`. Global pi-vcc config and `PI_VCC_CONFIG_PATH` are intentionally ignored.
154
155
 
155
156
  ## Visible Refiner overlay
156
157
 
157
158
  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
 
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.
160
+ - **Pi-btw-aligned geometry.** Each round uses `width: "78%"`, `minWidth: 72`, `maxHeight: "78%"`, `anchor: "top-center"`, and `{ top: 1, left: 2, right: 2 }` margins (no dependency on `pi-btw`; the renderer is built on Pi's public `pi-tui` primitives).
161
+ - **Complete streaming transcript.** Assistant text, thinking blocks, tool calls, tool results, and stderr are merged per turn/content block into lane entries without overlay-facing truncation; only the viewport slices them. Final `message_end` / `tool_execution_end` overwrite the live snapshot with the authoritative content.
162
+ - **No input row.** The overlay has no composer, no submit path, and no printable input forwarding. Only `Esc`, `Tab`/`Shift+Tab`, `↑/↓`, `PgUp/PgDn`, and SGR mouse wheel are consumed; everything else is ignored by design.
163
+ - **Equal-height panes for `reviewers: 3`.** Concurrent reviewer rounds render one reviewer lane per subagent as three independent equal-height panes inside the same top-center overlay; each pane keeps its own scroll offset and `follow-bottom` state. `Tab`/`Shift+Tab` switches the focused pane and scroll keys only affect it.
164
+ - **Lifecycle.** The overlay opens at round start, advances via the JSONL progress feed emitted by `pi --mode json`, and is dismissed by `Esc`. `Esc` is close-only — it never aborts the refiner child; the child keeps running to natural completion and its result still flows back through the tool result path. A new refinement round replaces any retained finished overlay to avoid overlay stacking. Cancelled and timed-out children render as terminal states with the original error message — never as silent drops.
162
165
  - **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.
163
166
 
164
167
  ## The execution rules
@@ -214,18 +217,65 @@ or register the absolute path in `~/.pi/agent/settings.json`:
214
217
  { "extensions": ["/absolute/path/to/pi-plans"] }
215
218
  ```
216
219
 
220
+ ## Code graph (v0.3+)
221
+
222
+ `/init-graph` walks the worktree, parses JavaScript/TypeScript and Python
223
+ files with Tree-sitter, and stores a normalized function graph in
224
+ `.git/pi_plans/code_graph.db`. The DB is the canonical source for downstream
225
+ agents: function rows expose a low-token `description`/`inputs`/`outputs`
226
+ JSON view, call edges are normalized with `in_links`/`out_links` derived on
227
+ read, and each function retains its full UTF-8 source.
228
+
229
+ - `/init-graph [--reindex] [--no-summary] [--no-commit]` — scan the worktree and write the graph; if `code_graph.db` already exists, it first asks whether to rebuild or sync changed paths via `/update-graph` (non-interactive runs and `--reindex` stay on the rebuild path). With `--reindex` it keeps the existing full-worktree reindex semantics on the rebuild path. Dirty trees get a `chore(code-graph): pre-init snapshot` commit first (`--no-commit` skips). After indexing it records a `code_graph_snapshot` (HEAD + uncommitted paths) that `/graph-drift` compares against. The parser dependencies are installed via npm (`tree-sitter`, grammars).
230
+ - `/graph-status` — print function/file/edge counts.
231
+ - `/update-graph [--dry-run] [--base <commit>]` — incrementally reindex only the paths `git status --porcelain` reports (including untracked and rename targets); deleted files' DB rows are purged, never resurrected. `/init-graph` uses this path when you choose the sync-changes branch.
232
+ - `/graph-drift [--json] [--commit-aware]` — direction-aware convergence check:
233
+ (a) per-file hash match or a pending apply marker, (b) every uncommitted
234
+ indexable path is indexed, (c) snapshot vs current HEAD (informational).
235
+ - `/apply-graph [--force]` — materialize DB edits back to source. Files with
236
+ `pending_kind='update'` are written (created when missing on disk);
237
+ `pending_kind='delete'` files are removed from disk and the DB; pending-null
238
+ missing files are skipped, never resurrected. Refuses when the active
239
+ planning run is `planning`/`accepted`.
240
+ - `/enable-graph` / `/disable-graph` — toggle the `graph_enabled` config
241
+ flag (disable refuses while drift is dirty). When enabled, planner/refiner/
242
+ executor prompts hard-require function-level reads for indexed code, and the
243
+ built-in `read`/`write`/`edit` tools become graph-aware overrides for
244
+ indexed source files: `read` returns a capped function digest (≤50 lines,
245
+ synthetic anonymous entries folded) with `full: true` as the only whole-file
246
+ exit (small/zero-function files return full text); `write`/`edit` stage
247
+ DB-first mutations; unexpected fallbacks (not indexed / runtime unavailable /
248
+ config unreadable) are marked in the result; refiner and criticizer
249
+ subagents get the `code_graph` tool in their allowlist (verified in headless
250
+ no-session children); the executor loop
251
+ becomes DB-first: `code_graph` mutations → `/apply-graph` → `/graph-drift`
252
+ → `plans final-commit` → `/init-graph`.
253
+ - The `code_graph` tool provides read-only screening (`status`, `screening`,
254
+ `get-function`, `manifest`), DB-first mutations (`update-function`,
255
+ `update-file`, `delete-file` — all mark files `pending_materialization`
256
+ and append to `change_log`), and `list-pending` so agents can navigate and
257
+ edit the graph without pulling `full_code`.
258
+
259
+ Runtime requirements: the base extension still requires Node ≥ 22.6; the
260
+ graph feature additionally requires Node ≥ 22.13 (or `--experimental-sqlite`)
261
+ so that `node:sqlite` is available without flag. Pi's host currently ships
262
+ Node ≥ 22.19, so a fresh install works out of the box. On unsupported
263
+ runtimes (Bun, missing parsers) graph commands fail locally without
264
+ affecting the planning workflow.
265
+
217
266
  ## Layout
218
267
 
219
268
  ```
220
269
  pi-plans/
221
270
  ├── index.ts # Extension entry: tools, commands, guard, execution loop
222
- ├── tools/ # plans, ask-choice, refine, execute-plan
271
+ ├── tools/ # plans, ask-choice, refine, execute-plan, code-graph
223
272
  ├── src/ # state, guard, plan parsing, subagent runner, refine overlay, exec loop
273
+ │ └── code-graph/ # SQLite schema/store, parsers, indexer, summary, materialize
224
274
  ├── skills/ # The planning router plus five specialist planning skills
225
275
  ├── references/ # Shared workflow, state/config, plan template (normative)
226
276
  ├── agents/ # reviewer.md / criticizer.md subagent prompts
227
277
  ├── scripts/validate.ts # Structure validator
228
- └── tests/ # node:test suite (state, guard, plan parsing, execution, refine progress)
278
+ └── tests/ # node:test suite (state, guard, plan parsing, execution, refine progress, code-graph)
229
279
  ```
230
280
 
231
281
  ## Safety model
@@ -239,7 +289,10 @@ npm run validate # structure validator
239
289
  npm test # node:test suite (stdlib only, no deps)
240
290
  ```
241
291
 
242
- Both run on Node ≥ 22.6 via `--experimental-strip-types`; no npm dependencies.
292
+ Both run on Node ≥ 22.6 via `--experimental-strip-types`. The graph
293
+ extension additionally requires `node:sqlite` (Node ≥ 22.13 unflagged, or
294
+ any Node ≥ 22.6 with `--experimental-sqlite`) and the four parser
295
+ dependencies listed in `dependencies`.
243
296
 
244
297
  ## FAQ
245
298
 
@@ -249,7 +302,7 @@ The plan is the contract. Refinement converges on scope while nothing is writabl
249
302
 
250
303
  **What can Auto-complete decide on my behalf?**
251
304
 
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.
305
+ 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. After execution completes, interactive sessions enter goal-running review mode automatically and ask only for the implementation-review loop's termination condition; the loop then continues until that condition or the 5-round cap. Headless sessions stay silent.
253
306
 
254
307
  **Where does all the state live?**
255
308
 
package/index.ts CHANGED
@@ -32,6 +32,8 @@ import {
32
32
  handlePlanningBeforeCompact,
33
33
  handlePlanningCompact,
34
34
  handlePlanningCompactFailed,
35
+ noteCompactionEnded,
36
+ noteCompactionStarted,
35
37
  PLANNING_PLAN_WRITTEN_CUSTOM_TYPE,
36
38
  registerExecutionTurnHandlers,
37
39
  refreshPlanningCompactionCooldown,
@@ -50,8 +52,20 @@ import {
50
52
  } from "./src/autocomplete.ts";
51
53
  import { planningWriteBlockReason } from "./src/guard.ts";
52
54
  import { registerQueryInterviewHooks } from "./src/query-hook.ts";
55
+ import { registerCodeGraphTool } from "./tools/code-graph.ts";
56
+ import { registerGraphAwareFileTools } from "./tools/graph-aware-file-tools.ts";
57
+ import {
58
+ initGraphCommand,
59
+ applyGraphCommand,
60
+ graphStatusCommand,
61
+ updateGraphCommand,
62
+ graphDriftCommand,
63
+ enableGraphCommand,
64
+ disableGraphCommand,
65
+ } from "./src/code-graph/commands.ts";
53
66
  import { latestPlanVersion, nextPlanVersionPath } from "./src/plan.ts";
54
- import { getRun, readActive, recordDecision, resolveStateRootOrNull, setRunStatus } from "./src/state.ts";
67
+ import { configPiPlansCommand } from "./src/config-command.ts";
68
+ import { getRun, loadConfig, readActive, recordDecision, resolveStateRootOrNull, setRunStatus } from "./src/state.ts";
55
69
  import { registerAskChoiceTool } from "./tools/ask-choice.ts";
56
70
  import { executeHandoff, registerExecutePlanTool } from "./tools/execute-plan.ts";
57
71
  import { registerPlansTool } from "./tools/plans.ts";
@@ -67,12 +81,17 @@ const extensionLoadedAt = new Date();
67
81
  function extensionStalenessLine(): string {
68
82
  try {
69
83
  const dirs = [baseDir, path.join(baseDir, "src"), path.join(baseDir, "tools")];
84
+ const stack: string[] = [...dirs];
70
85
  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;
86
+ while (stack.length) {
87
+ const dir = stack.pop()!;
88
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
89
+ const full = path.join(dir, entry.name);
90
+ if (entry.isDirectory()) stack.push(full);
91
+ else if (entry.isFile() && entry.name.endsWith(".ts")) {
92
+ const mtime = fs.statSync(full).mtimeMs;
93
+ if (mtime > newest) newest = mtime;
94
+ }
76
95
  }
77
96
  }
78
97
  if (newest > extensionLoadedAt.getTime() + 2000) {
@@ -98,6 +117,8 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
98
117
  registerRefineTool(pi, baseDir);
99
118
  registerExecutePlanTool(pi);
100
119
  registerQueryInterviewHooks(pi, hasActivePlanningWorkflow);
120
+ registerCodeGraphTool(pi);
121
+ registerGraphAwareFileTools(pi);
101
122
 
102
123
  // Contribute the router skill plus the five specialist planning skills.
103
124
  pi.on("resources_discover", () => ({
@@ -162,6 +183,32 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
162
183
  return;
163
184
  });
164
185
 
186
+ // Bidirectional code-graph reminder hook: separate from the planning guard
187
+ // above (which early-returns during execution). Fires only when the graph
188
+ // is enabled and an execution is active. Reminders are best-effort notifies.
189
+ pi.on("tool_call", async (event, ctx) => {
190
+ if (!getExecution()) return;
191
+ const stateRoot = resolveStateRootOrNull(ctx.cwd);
192
+ if (!stateRoot) return;
193
+ let graphEnabled = false;
194
+ try {
195
+ graphEnabled = loadConfig(stateRoot).graph_enabled === true;
196
+ } catch {
197
+ return;
198
+ }
199
+ if (!graphEnabled) return;
200
+ if (event.toolName === "edit" || event.toolName === "write") {
201
+ ctx.ui?.notify?.("code-graph: source edited directly — run /update-graph to sync the graph, or use code_graph mutations + /apply-graph for DB-first edits", "info");
202
+ return;
203
+ }
204
+ if (event.toolName === "code_graph") {
205
+ const action = String((event.input as { action?: string }).action ?? "");
206
+ if (action === "update-function" || action === "update-file" || action === "delete-file") {
207
+ ctx.ui?.notify?.("code-graph: mutation staged — run /apply-graph to materialize, then /graph-drift to verify", "info");
208
+ }
209
+ }
210
+ });
211
+
165
212
  pi.on("context", (event) => {
166
213
  const filteredExecution = filterExecutionResumeMessages(event.messages as Array<{ customType?: string }>);
167
214
  const messages = filterPlanningResumeMessages(filteredExecution);
@@ -171,17 +218,20 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
171
218
  });
172
219
 
173
220
  pi.on("session_before_compact", async (event, ctx) => {
221
+ noteCompactionStarted(ctx, event.customInstructions);
174
222
  const executionResult = await handleExecutionBeforeCompact(pi, ctx, event);
175
223
  if (executionResult) return executionResult;
176
224
  return handlePlanningBeforeCompact(pi, ctx, event);
177
225
  });
178
226
  pi.on("session_compact", async (event, ctx) => {
179
- handleExecutionCompact(pi, ctx, event);
180
- handlePlanningCompact(pi, ctx, event);
227
+ await handleExecutionCompact(pi, ctx, event);
228
+ await handlePlanningCompact(pi, ctx, event);
229
+ noteCompactionEnded(ctx, event.customInstructions);
181
230
  });
182
231
  pi.on("session_compact_failed", async (event, ctx) => {
183
232
  handleExecutionCompactFailed(pi, ctx, event);
184
233
  handlePlanningCompactFailed(pi, ctx, event);
234
+ noteCompactionEnded(ctx, event.customInstructions);
185
235
  });
186
236
 
187
237
  // Flush points for deferred execution-loop writes: primary drain when the
@@ -197,7 +247,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
197
247
  // -----------------------------------------------------------------------
198
248
  pi.on("before_agent_start", async (_event, ctx) => {
199
249
  drainExecutionFlush(pi, ctx);
200
- const content = executionContextMessage();
250
+ const content = executionContextMessage(ctx);
201
251
  if (!content) {
202
252
  if (!getExecution() && shouldTriggerPlanningCompaction(ctx)) {
203
253
  requestPlanningCompaction(ctx);
@@ -235,6 +285,55 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
235
285
  // Commands
236
286
  // -----------------------------------------------------------------------
237
287
 
288
+ pi.registerCommand("init-graph", {
289
+ description: "Index the worktree into .git/pi_plans/code_graph.db. If a graph DB already exists, prompt to rebuild or sync changed paths via /update-graph; `--reindex` and non-interactive runs stay on the rebuild path.",
290
+ handler: async (args, ctx) => {
291
+ await initGraphCommand(args, ctx);
292
+ },
293
+ });
294
+
295
+ pi.registerCommand("apply-graph", {
296
+ description: "Apply code_graph.db changes back to source. Refuses during active planning/accepted run.",
297
+ handler: async (args, ctx) => {
298
+ await applyGraphCommand(args, ctx);
299
+ },
300
+ });
301
+
302
+ pi.registerCommand("graph-status", {
303
+ description: "Show code graph counts (functions, files, edges).",
304
+ handler: async (args, ctx) => {
305
+ await graphStatusCommand(args, ctx);
306
+ },
307
+ });
308
+
309
+ pi.registerCommand("update-graph", {
310
+ description: "Incrementally reindex working-tree changes (git status porcelain, incl. untracked/renames) into code_graph.db. Also used by /init-graph when you choose the sync-changes branch. Flags: --dry-run, --base <commit>.",
311
+ handler: async (args, ctx) => {
312
+ await updateGraphCommand(args, ctx);
313
+ },
314
+ });
315
+
316
+ pi.registerCommand("graph-drift", {
317
+ description: "Check DB↔source convergence (hash/pending, uncommitted coverage, snapshot). Flags: --json, --commit-aware.",
318
+ handler: async (args, ctx) => {
319
+ await graphDriftCommand(args, ctx);
320
+ },
321
+ });
322
+
323
+ pi.registerCommand("enable-graph", {
324
+ description: "Enable the code graph: agents prefer graph reads and DB-first edits.",
325
+ handler: async (_args, ctx) => {
326
+ await enableGraphCommand(_args, ctx);
327
+ },
328
+ });
329
+
330
+ pi.registerCommand("disable-graph", {
331
+ description: "Disable the code graph (refuses while DB/source drift is dirty).",
332
+ handler: async (_args, ctx) => {
333
+ await disableGraphCommand(_args, ctx);
334
+ },
335
+ });
336
+
238
337
  pi.registerCommand("plans", {
239
338
  description: "Show pi-plans state: config, active run, and execution progress",
240
339
  handler: async (_args, ctx) => {
@@ -264,6 +363,13 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
264
363
  },
265
364
  });
266
365
 
366
+ pi.registerCommand("config-pi-plans", {
367
+ description: "Re-ask and update pi-plans workspace config: language, artifact root, graph, reviewer, and criticizer defaults",
368
+ handler: async (args, ctx) => {
369
+ await configPiPlansCommand(args, ctx);
370
+ },
371
+ });
372
+
267
373
  pi.registerCommand("plans-autocomplete-stop", {
268
374
  description: "Stop Auto-complete for the active planning run",
269
375
  handler: async (_args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-plans",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Human-in-the-loop planning extension for the Pi coding agent: researched, refined Markdown plans before any code changes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -54,5 +54,11 @@
54
54
  "validate": "node --experimental-strip-types scripts/validate.ts",
55
55
  "test": "node --experimental-strip-types scripts/run-tests.ts",
56
56
  "prepack": "npm run validate && npm test"
57
+ },
58
+ "dependencies": {
59
+ "tree-sitter": "^0.25.1",
60
+ "tree-sitter-javascript": "^0.25.0",
61
+ "tree-sitter-python": "^0.25.0",
62
+ "tree-sitter-typescript": "^0.23.2"
57
63
  }
58
64
  }
@@ -9,9 +9,10 @@ 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; 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;
12
+ - `refine` spawns read-only Pi subagents (`pi --mode json -p --no-session --tools read,grep,find,ls`, plus `code_graph` for both roles when workspace `graph_enabled` is true) with isolated context; delegated Reviewer/Criticizer runs show a standalone aggregate overlay titled `Reviewer` or `Criticizer` (78% × 78% top-center, ≥72 cols, no input row), stream assistant/thinking/tool events into per-lane transcripts with follow-bottom scroll, dismiss on `Esc` (close-only the refiner child keeps running and its result still flows back as tool output), replace any retained finished overlay when a new round begins, and return conclusions to the main session as tool output;
13
+ - when graph mode is enabled, graph-aware `read`/`edit` overrides are active for indexed source files: `read` returns a capped function digest (≤50 lines, synthetic anonymous entries folded) by default — drill in via `offset/limit` or `code_graph get-function`, and `full: true` is the only whole-file exit (small/zero-function files return full text; safety truncation matches native read); `write`/`edit` stage DB-first mutations until `/apply-graph`; unexpected fallbacks (`not indexed` / `runtime unavailable` / `config read failed`) are marked at the top of the result while flag-off fallbacks stay unmarked;
13
14
  - 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;
15
+ - execution and planning compaction keep Pi's SessionManager as the history owner; during active pi-plans runs, `session_before_compact` uses a deterministic no-LLM VCC-style summary with `[Session Goal]`, `[Files And Changes]`, `[Commits]`, `[Outstanding Context]`, `[User Preferences]`, and a ranked brief transcript; Pi core owns manual `/compact`, threshold, and overflow scheduling, while pi-plans handles smart tail keep, `keep:N`, stats, and phase-specific run/plan/current-I/checklist context;
15
16
 
16
17
  ## Planning Boundary
17
18
 
@@ -129,13 +130,27 @@ A refinement round is complete when all reviewer outputs have returned or all cr
129
130
  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:
130
131
 
131
132
  - 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;
133
+ - execution-phase compaction is handled only when Pi core emits manual `/compact`, threshold, or overflow events; summaries are deterministic VCC-style summaries, include session-derived plan/current-I/checklist context, use smart tail keep and `keep:N`, and never call a model or request proactive current-I compaction;
133
134
  - the read-only guard lifts: full write access returns;
134
135
  - the run status moves to `executing`, then `done` when the last `[DONE:VC-xxx]` marker lands;
135
136
  - `/plans-stop` stops execution; `/plans` shows progress.
136
137
 
137
138
  If the user declines, stay in planning (or stop, per their choice). Never start implementation without the approved handoff.
138
139
 
140
+ ### Post-Execution Continuation
141
+
142
+ When execution completes in an interactive session, the completion message attaches a goal-running continuation block and triggers a new agent turn so the model can enter the implementation-review loop immediately. The interactive-only trigger keeps headless sessions silent (no unconsented subagent cost). The same behavior applies on both completion call sites (the normal `turn_end` completion and the `restoreFromSession` recovery path).
143
+
144
+ The agent then asks one `ask_choice` question for the termination condition of the implementation-review loop:
145
+
146
+ - `autoComplete: false` — suppresses the run-scoped Auto-complete mode for this question.
147
+ - Options (recommended first): 1. until no high-severity finding (hard cap 5 rounds) 2. 1 round 3. 2 rounds 4. 3 rounds.
148
+ - Each refinement round calls `refine` with `role: "reviewer", target: "implementation"`, accepts findings on evidence, applies fixes, re-runs relevant tests, and records progress. The hard cap is 5 rounds regardless of the chosen termination condition.
149
+ - Round audit trail: `decisions.jsonl`, `subagents.jsonl`, and `pi-plans-ameliorate` entries (one at goal start, then one per round) carry `currentRound` for post-hoc verification.
150
+ - Headless sessions skip the prompt entirely; no `pi-plans-ameliorate` entry is appended.
151
+
152
+ `refine` accepts a `target` parameter (`"plan"` default, `"implementation"` for the post-execution loop). The implementation brief anchors findings to the plan's goals and acceptance criteria, explicitly assesses delivery maturity (MVP-only vs. long-term refinement: stopgaps, missing tests, technical debt, production readiness), and tags out-of-scope improvements as low severity.
153
+
139
154
  ## Red Flags
140
155
 
141
156
  Stop and return to the workflow if any of these happen:
@@ -25,6 +25,7 @@ Bare repositories are refused with a clear error. A missing `git` executable is
25
25
  ```text
26
26
  <git-common-dir>/pi_plans/
27
27
  config.json
28
+ pi-vcc-config.json
28
29
  active.json
29
30
  runs/
30
31
  <run-id>/
@@ -36,7 +37,7 @@ Bare repositories are refused with a clear error. A missing `git` executable is
36
37
  cache/
37
38
  ```
38
39
 
39
- `config.json` is stable workspace preference state. `active.json` and `runs/` are run state. Large external references stay outside the repository by default under `~/.cache/pi-plans/refs/`, with metadata recorded in the run state and public artifacts.
40
+ `config.json` is stable workspace preference state. `pi-vcc-config.json` is the repo-private compaction config used only by pi-plans' VCC-style compact hook. `active.json` and `runs/` are run state. Large external references stay outside the repository by default under `~/.cache/pi-plans/refs/`, with metadata recorded in the run state and public artifacts.
40
41
 
41
42
  ## Config Schema
42
43
 
@@ -76,6 +77,30 @@ Rules:
76
77
  - `artifact_root_updated_at` is the selection timestamp or `null` before confirmation.
77
78
  - There is intentionally no `effort` field: subagents inherit the dispatching session's model and thinking level unless an exact selector is stored. The real lever is the main session's thinking level at refine time.
78
79
 
80
+ ## VCC Compact Config
81
+
82
+ `pi-vcc-config.json` is scaffolded under the resolved `<git-common-dir>/pi_plans/` state root when an active planning or execution compaction hook first needs it. It is independent from `config.json` so planning preferences, run state, and compact policy can evolve separately.
83
+
84
+ Default values:
85
+
86
+ ```json
87
+ {
88
+ "overrideDefaultCompaction": true,
89
+ "smartKeepTail": true,
90
+ "continueAfterThresholdCompact": true,
91
+ "debug": false
92
+ }
93
+ ```
94
+
95
+ Rules:
96
+
97
+ - Only the repo-private file is read. Upstream global pi-vcc config such as `~/.pi/agent/pi-vcc-config.json` and `PI_VCC_CONFIG_PATH` are ignored.
98
+ - Missing files are created with defaults; valid files keep user values and receive missing default keys; invalid JSON is never clobbered and the runtime falls back to defaults for that read.
99
+ - `overrideDefaultCompaction:false` returns ordinary Pi manual/threshold/overflow compactions to Pi core. Explicit pi-plans internal compact hints can still use the VCC path.
100
+ - `smartKeepTail:true` starts from the requested/default keep count and may retain more recent user turns when the retained tail remains within the safe token budget. Explicit `keep:N` is honored.
101
+ - `continueAfterThresholdCompact:true` permits one hidden continuation after successful threshold/overflow compaction only on Pi versions that still need extension-driven resume behavior. Plain manual `/compact` never auto-continues, and `/compact <text>` sends the text once as the follow-up prompt.
102
+ - `debug:false` writes no diagnostics; `debug:true` writes a best-effort `/tmp/pi-vcc-debug.json` snapshot for local troubleshooting.
103
+
79
104
  ## Language Setting
80
105
 
81
106
  Before the first product planning question, check the persisted config (`plans` action `show`). If `language.tag` is missing or invalid, ask exactly one `ask_choice` question:
@@ -88,7 +113,14 @@ Before the first product planning question, check the persisted config (`plans`
88
113
 
89
114
  Persist with `plans` (`set-language`, `languageSource: "user"`). Use the selected language for visible questions, choices, review summaries, criticizer questions, and Markdown artifacts. Keep IDs, file paths, command names, JSON keys, and protocol labels stable in English.
90
115
 
91
- ## Planning Docs Location
116
+ ## Code Graph Enabled
117
+
118
+ `graph_enabled` (`boolean | null`) records whether the workspace wants graph-aware read/write/edit wrappers and `code_graph` mutations for indexed source files. `null` means the question was never asked: the first `plans` `init`/`show` in a workspace returns a `hint` instructing the agent to ask the user once via `ask_choice` (recommended: yes) and persist with the `plans` tool (`set-graph-enabled`, `enabled: true|false`). This question does not count against the planning-question limit. `/enable-graph` and `/disable-graph` toggle it later; disable refuses while graph drift is dirty.
119
+
120
+ ## `/config-pi-plans`
121
+
122
+ `/config-pi-plans` is an interactive workspace configuration wizard. It re-asks the workspace language, planning docs root, code graph toggle, reviewer mode/model, and criticizer mode/model, then writes the chosen defaults back to `.git/pi_plans/config.json`. When code graph is enabled, the extension also overrides built-in `read`/`write`/`edit` for indexed source files so graph-backed source reads and DB-first edits happen automatically. Model pickers can reuse the current session model, any available selector surfaced by `ctx.scopedModels` or the model registry, or a manually entered exact `provider/model` string. If a run is already active, only the workspace defaults change; the active run's `artifact_dir` and `language_tag` stay unchanged.
123
+
92
124
 
93
125
  Before the first product planning question, check the persisted config again. If `artifact_root_source` is missing or `unset`, ask exactly one `ask_choice` question:
94
126
 
@@ -22,11 +22,15 @@ const REQUIRED_TOOL_FILES = [
22
22
  "tools/ask-choice.ts",
23
23
  "tools/refine.ts",
24
24
  "tools/execute-plan.ts",
25
+ "tools/code-graph.ts",
25
26
  "src/state.ts",
26
27
  "src/guard.ts",
27
28
  "src/plan.ts",
28
29
  "src/subagent.ts",
29
30
  "src/exec.ts",
31
+ "src/code-graph/runtime.ts",
32
+ "src/code-graph/schema.ts",
33
+ "src/code-graph/store.ts",
30
34
  ];
31
35
 
32
36
  const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;