pi-plans 0.6.0 → 0.7.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 (109) hide show
  1. package/CONTRIBUTING.md +3 -3
  2. package/README.md +39 -37
  3. package/agents/reviewer.md +12 -3
  4. package/index.ts +42 -35
  5. package/package.json +1 -1
  6. package/references/pi-planning-workflow.md +44 -60
  7. package/references/plan-artifact-template.md +71 -60
  8. package/references/state-and-config.md +59 -43
  9. package/scripts/bench/pi-adapter/pi_plans_bench.py +33 -22
  10. package/scripts/bench/pi-adapter/rpc_driver.mjs +4 -4
  11. package/scripts/run-tests.ts +12 -1
  12. package/scripts/validate.ts +20 -9
  13. package/skills/debug-and-plan/SKILL.md +3 -3
  14. package/skills/plan-big/SKILL.md +3 -3
  15. package/skills/plan-normal/SKILL.md +3 -3
  16. package/skills/plan-small/SKILL.md +4 -4
  17. package/skills/plan-with-refs/SKILL.md +6 -6
  18. package/skills/planning/SKILL.md +1 -1
  19. package/src/ask-form.ts +4 -4
  20. package/src/auditor.ts +126 -0
  21. package/src/auto-approve.ts +1 -1
  22. package/src/autocomplete.ts +19 -17
  23. package/src/code-graph/commands.ts +2 -2
  24. package/src/code-graph/community.ts +1 -1
  25. package/src/code-graph/paths.ts +1 -1
  26. package/src/code-graph/watch.ts +2 -2
  27. package/src/compaction.ts +3 -3
  28. package/src/config-command.ts +146 -73
  29. package/src/dashboard.ts +257 -0
  30. package/src/exec.ts +692 -919
  31. package/src/global-state.ts +304 -0
  32. package/src/guard.ts +18 -19
  33. package/src/messaging.ts +44 -0
  34. package/src/plan.ts +421 -112
  35. package/src/query-hook.ts +4 -4
  36. package/src/refine-prompts.ts +12 -70
  37. package/src/refine-ui-helpers.ts +24 -5
  38. package/src/refine-ui-state.ts +1 -1
  39. package/src/refine-ui.ts +1 -1
  40. package/src/resume-command.ts +34 -128
  41. package/src/role-panels.ts +542 -0
  42. package/src/run-context.ts +3 -10
  43. package/src/state.ts +272 -72
  44. package/src/subagent.ts +19 -29
  45. package/src/task-tool.ts +100 -0
  46. package/src/tasks.ts +189 -0
  47. package/src/thinking-levels.ts +67 -0
  48. package/src/ui-language.ts +3 -54
  49. package/src/workflow-state.ts +63 -58
  50. package/tests/analyze-refs.test.ts +35 -18
  51. package/tests/ask-choice-schema.test.ts +0 -12
  52. package/tests/ask-choice.test.ts +2 -49
  53. package/tests/ask-form-tool.test.ts +4 -5
  54. package/tests/ask-form.test.ts +2 -2
  55. package/tests/auditor.test.ts +111 -0
  56. package/tests/auto-approve.test.ts +7 -10
  57. package/tests/autocomplete.test.ts +8 -11
  58. package/tests/code-graph-apply-action.test.ts +2 -2
  59. package/tests/code-graph-commands.test.ts +2 -2
  60. package/tests/code-graph-index.test.ts +2 -2
  61. package/tests/code-graph-loop.e2e.test.ts +1 -1
  62. package/tests/code-graph-mutations.test.ts +1 -1
  63. package/tests/code-graph-rollback.test.ts +1 -1
  64. package/tests/code-graph-v05.test.ts +2 -2
  65. package/tests/compaction.test.ts +1 -1
  66. package/tests/config-command.test.ts +103 -100
  67. package/tests/dashboard.test.ts +268 -0
  68. package/tests/exec-lifecycle.test.ts +181 -115
  69. package/tests/exec-panel-lifecycle.test.ts +106 -251
  70. package/tests/exec.test.ts +617 -1706
  71. package/tests/execute-plan.test.ts +44 -19
  72. package/tests/extension-load.test.ts +48 -0
  73. package/tests/global-state.test.ts +371 -0
  74. package/tests/graph-aware-file-tools.test.ts +5 -5
  75. package/tests/guard.test.ts +1 -1
  76. package/tests/multi-run.test.ts +3 -103
  77. package/tests/plan.test.ts +139 -62
  78. package/tests/plans.test.ts +7 -79
  79. package/tests/refine-prompts.test.ts +20 -71
  80. package/tests/refine-resume.test.ts +27 -22
  81. package/tests/refine-ui.test.ts +6 -15
  82. package/tests/resume-lifecycle.test.ts +37 -22
  83. package/tests/resume.test.ts +33 -81
  84. package/tests/role-panels.test.ts +391 -0
  85. package/tests/run-context.test.ts +1 -1
  86. package/tests/run-ownership.test.ts +1 -1
  87. package/tests/stale-ctx.test.ts +218 -0
  88. package/tests/state.test.ts +151 -32
  89. package/tests/subagent-thinking.test.ts +65 -0
  90. package/tests/subagent-usage.test.ts +1 -1
  91. package/tests/task-tool.test.ts +61 -0
  92. package/tests/thinking-levels.test.ts +77 -0
  93. package/tests/ui-language.test.ts +2 -17
  94. package/tests/workflow-state.test.ts +17 -99
  95. package/tools/analyze-refs.ts +67 -32
  96. package/tools/ask-choice.ts +7 -53
  97. package/tools/code-graph.ts +2 -2
  98. package/tools/execute-plan.ts +48 -99
  99. package/tools/graph-aware-file-tools.ts +4 -10
  100. package/tools/plans.ts +40 -66
  101. package/tools/refine.ts +101 -164
  102. package/agents/criticizer.md +0 -18
  103. package/agents/executor.md +0 -26
  104. package/scripts/bench/pi-adapter/__pycache__/pi_plans_bench.cpython-312.pyc +0 -0
  105. package/src/panel.ts +0 -473
  106. package/src/termination-prompt.ts +0 -73
  107. package/tests/goal-wait.test.ts +0 -269
  108. package/tests/panel-i-zero.test.ts +0 -420
  109. package/tests/panel.test.ts +0 -355
@@ -1,6 +1,9 @@
1
1
  /**
2
- * `execute_plan` tool — the execution handoff. On explicit user approval (no
3
- * Auto-complete) the extension enters execution mode with checklist tracking.
2
+ * `execute_plan` tool — the execution handoff (v0.6.1). On explicit user
3
+ * approval (no Auto-complete) the extension enters task-tree execution mode:
4
+ * progress flows through `plans_update_task`, the dashboard tracks every
5
+ * task, and the completion auditor gates the final pass. Legacy I-### plans
6
+ * parse through the fallback with an upgrade notice.
4
7
  */
5
8
 
6
9
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -12,15 +15,13 @@ import {
12
15
  getExecution,
13
16
  resumeActiveExecution,
14
17
  startExecution,
15
- type ExecutionRuntime,
16
18
  } from "../src/exec.ts";
17
19
  import { disableAutoComplete } from "../src/autocomplete.ts";
18
20
  import { isAutoApproveEnabled } from "../src/auto-approve.ts";
19
- import { latestPlanVersion, parseChecklist, parseImplItems } from "../src/plan.ts";
20
- import { normalizeWorkdir, recordDecision, type RunSummary } from "../src/state.ts";
21
- import { bindRun, resolveActiveRun } from "../src/run-context.ts";
21
+ import { checklistHeaderName, latestPlanVersion, lintPlanTasks, parseChecklist, parsePlanTasks } from "../src/plan.ts";
22
+ import { normalizeWorkdir, type RunSummary } from "../src/state.ts";
23
+ import { bindRun } from "../src/run-context.ts";
22
24
  import { executionCandidates, resolveCommandRun } from "../src/run-picker.ts";
23
- import { collectModelSelectors, modelSelectorOf } from "../src/config-command.ts";
24
25
  import { resolveUiLanguage } from "../src/ui-language.ts";
25
26
 
26
27
 
@@ -43,7 +44,7 @@ export async function executeHandoff(
43
44
  ctx: ExtensionContext,
44
45
  planPathArg?: string,
45
46
  workdirArg?: string,
46
- signal?: AbortSignal,
47
+ _signal?: AbortSignal,
47
48
  ): Promise<HandoffOutcome> {
48
49
  const workdir = normalizeWorkdir(workdirArg ?? ctx.cwd);
49
50
 
@@ -79,10 +80,28 @@ export async function executeHandoff(
79
80
  if (items.length === 0) {
80
81
  return {
81
82
  status: "error",
82
- message: `${planPath} has no parsable \`## Verifier Checklist\` with \`- [ ] \`VC-###\` ...\` items. Fix the plan before execution.`,
83
+ message: `${planPath} has no parsable \`## Verification Checks\` (or legacy \`## Verifier Checklist\`) with \`- [ ] \`VC-###\` ...\` items. Fix the plan before execution.`,
83
84
  };
84
85
  }
85
- const implItems = parseImplItems(planText);
86
+ const planTasks = parsePlanTasks(planText);
87
+ if (planTasks.tasks.length === 0) {
88
+ return {
89
+ status: "error",
90
+ message: `${planPath} has no parsable tasks: add a \`## Tasks\` section (\`- \`Task-1\`: title — deps: …; files: …; wave: 1\`).`,
91
+ };
92
+ }
93
+ // I-001/R-001: task-tree consistency is advisory while planning and
94
+ // hard-rejected at this gate. Legacy I-### fallback plans are exempt
95
+ // (their shape predates the microsyntax).
96
+ if (!planTasks.legacy) {
97
+ const lint = lintPlanTasks(planText);
98
+ if (lint !== null) {
99
+ return {
100
+ status: "error",
101
+ message: `${planPath} failed the task-tree consistency gate; fix these before execution:\n${lint}`,
102
+ };
103
+ }
104
+ }
86
105
 
87
106
  disableAutoComplete(ctx, "execution handoff");
88
107
  // I-004/D-019: PI_PLANS_AUTO_APPROVE=1 short-circuits the confirm BEFORE
@@ -96,14 +115,22 @@ export async function executeHandoff(
96
115
  };
97
116
  }
98
117
 
118
+ const legacyPlan = planTasks.legacy || checklistHeaderName(planText) === "Verifier Checklist";
119
+
99
120
  let approved: boolean;
100
121
  if (autoApprove) {
101
122
  approved = true;
102
123
  } else {
124
+ const lang = resolveUiLanguage(workdir);
103
125
  const preview = items.map((item) => `- ${item.done ? "☑" : "☐"} ${item.id}`).join("\n");
126
+ const legacyNote = legacyPlan
127
+ ? (lang === "zh"
128
+ ? "\n\n注意:该计划使用旧版 I-### 格式,将以兼容映射执行;建议在下次修订时升级为 ## Tasks 新格式。"
129
+ : "\n\nNote: this plan uses the legacy I-### format and executes through the compatibility mapping; upgrade it to the ## Tasks format at the next revision.")
130
+ : "";
104
131
  approved = await ctx.ui.confirm(
105
132
  "Execute this plan now?",
106
- `${planPath}\n${items.length} verifier item(s):\n${preview}\n\nExecution mode enables write access and tracks [DONE:VC-xxx] progress.`,
133
+ `${planPath}\n${items.length} verification check(s) over ${planTasks.tasks.length} task(s):\n${preview}${legacyNote}\n\nExecution mode enables write access; task progress is reported with the plans_update_task tool and gated by the completion auditor.`,
107
134
  );
108
135
  }
109
136
  if (!approved) {
@@ -114,81 +141,15 @@ export async function executeHandoff(
114
141
  // the approval checkpoint and status flip land on the run the user chose.
115
142
  if (chosenRun) bindRun(ctx.sessionManager, workdir, chosenRun.run_id);
116
143
 
117
- // v0.6.0 (R-8): runtime question — current session (recommended) or a
118
- // delegated executor on another model. Skipped under auto-approve/no-UI.
119
- const runtime = await chooseExecutionRuntime(ctx, workdir, autoApprove, chosenRun);
120
-
121
- await startExecution(getCurrentApi(), ctx, planPath, items, implItems, { runtime, signal });
122
- const scopeNote = implItems.length ? ` Tracking ${implItems.length} implementation item(s).` : "";
144
+ await startExecution(ctx, { planPath, planTasks, items });
123
145
  const autoNote = autoApprove ? "[auto-approve] " : "";
124
- const runtimeNote = runtime === "current-session" ? "" : ` Delegated to executor model ${runtime.modelSelector}; progress mirrors in the overlay.`;
146
+ const legacyNote = legacyPlan ? " Legacy I-### mapping active; upgrade the plan at the next revision." : "";
125
147
  return {
126
148
  status: "executing",
127
149
  planPath,
128
150
  itemCount: items.length,
129
- message: `${autoNote}Execution approved. ${items.length} verifier item(s) queued; implement in dependency order and mark verified items with [DONE:VC-xxx].${scopeNote}${runtimeNote}`,
130
- };
131
- }
132
-
133
- const MODEL_SELECTOR_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
134
-
135
- /**
136
- * R-8: ask where the execution runs. Uses ctx.ui.select directly (ask_choice
137
- * is a tool and cannot be invoked from tool/command context). Under
138
- * auto-approve or no-UI the question is skipped: current session, decision
139
- * recorded with the [auto-approve] annotation convention.
140
- */
141
- async function chooseExecutionRuntime(
142
- ctx: ExtensionContext,
143
- workdir: string,
144
- autoApprove: boolean,
145
- chosenRun: RunSummary | null,
146
- ): Promise<ExecutionRuntime> {
147
- const record = (answer: string, source: "user" | "auto-complete", question: string, options: string[]): void => {
148
- const runId = chosenRun?.run_id ?? resolveActiveRun(ctx.sessionManager, workdir)?.run_id ?? null;
149
- if (!runId) return;
150
- try {
151
- recordDecision(workdir, runId, {
152
- question,
153
- options,
154
- answer,
155
- answer_source: source,
156
- });
157
- } catch {
158
- /* decision audit trail is best-effort */
159
- }
151
+ message: `${autoNote}Execution approved. ${planTasks.tasks.length} task(s) queued in wave order; report progress with the plans_update_task tool (status + evidence); the completion auditor verifies every check before the run completes.${legacyNote}`,
160
152
  };
161
- if (autoApprove || !ctx.hasUI) {
162
- record("current session [auto-approve]", "auto-complete", "Execution runtime", ["current session", "switch model"]);
163
- return "current-session";
164
- }
165
- const lang = resolveUiLanguage(workdir);
166
- const currentLabel = lang === "zh" ? "使用当前会话(推荐)" : "Use the current session (recommended)";
167
- const switchLabel = lang === "zh" ? "切换至其他模型…" : "Switch to another model…";
168
- const title = lang === "zh" ? "执行运行时" : "Execution runtime";
169
- const first = await ctx.ui.select(title, [currentLabel, switchLabel]);
170
- if (first === undefined || first === currentLabel) {
171
- record("current session", "user", "Execution runtime", [currentLabel, switchLabel]);
172
- return "current-session";
173
- }
174
- // Model picker: switch targets exclude the current selector by design
175
- // (option 1 IS the current session).
176
- const currentSelector = modelSelectorOf(ctx.model);
177
- const targets = collectModelSelectors(ctx, currentSelector);
178
- const otherLabel = lang === "zh" ? "其他(输入 provider/model)…" : "Other (type provider/model)…";
179
- const modelTitle = lang === "zh" ? "切换至哪个模型执行?" : "Switch to which model?";
180
- let modelPick = await ctx.ui.select(modelTitle, [...targets, otherLabel]);
181
- if (modelPick === otherLabel) {
182
- const typed = await ctx.ui.input(modelTitle, "provider/model");
183
- modelPick = typed && MODEL_SELECTOR_RE.test(typed.trim()) ? typed.trim() : undefined;
184
- }
185
- if (modelPick === undefined || !MODEL_SELECTOR_RE.test(modelPick)) {
186
- // Cancelled or invalid: fall back to the current session, recorded.
187
- record("current session (model switch cancelled)", "user", "Execution runtime", [currentLabel, switchLabel]);
188
- return "current-session";
189
- }
190
- record(`switch model: ${modelPick}`, "user", "Execution runtime", [currentLabel, switchLabel, ...targets, otherLabel]);
191
- return { modelSelector: modelPick };
192
153
  }
193
154
 
194
155
  /** The user command may resume an approved execution; the tool always asks. */
@@ -196,40 +157,28 @@ export async function executeCommand(ctx: ExtensionContext, planPathArg?: string
196
157
  const activeExecution = getExecution();
197
158
  const planPath = planPathArg ? path.resolve(ctx.cwd, planPathArg.replace(/^@/, "")) : activeExecution?.planPath;
198
159
  if (activeExecution && planPath && path.resolve(activeExecution.planPath) === path.resolve(planPath)) {
199
- const resumed = resumeActiveExecution(getCurrentApi(), ctx);
160
+ const resumed = resumeActiveExecution(ctx);
200
161
  return {
201
162
  status: "executing",
202
163
  planPath,
203
164
  itemCount: activeExecution.items.length,
204
- message: resumed ? "Execution resumed; verified progress preserved." : "This plan is already executing.",
165
+ message: resumed ? "Execution resumed; task progress preserved." : "This plan is already executing.",
205
166
  };
206
167
  }
207
168
  return executeHandoff(ctx, planPathArg);
208
169
  }
209
170
 
210
- // The tool registers with the ExtensionAPI in scope; keep a module-level
211
- // reference so the shared handoff helper can reach appendEntry/sendMessage.
212
- let currentApi: ExtensionAPI | null = null;
213
- export function setCurrentApi(api: ExtensionAPI): void {
214
- currentApi = api;
215
- }
216
- function getCurrentApi(): ExtensionAPI {
217
- if (!currentApi) throw new Error("execute_plan used before extension initialization");
218
- return currentApi;
219
- }
220
-
221
- export function registerExecutePlanTool(pi: ExtensionAPI): void {
222
- setCurrentApi(pi);
223
- pi.registerTool({
171
+ export function registerExecutePlanTool(ext: ExtensionAPI): void {
172
+ ext.registerTool({
224
173
  name: "execute_plan",
225
174
  label: "Execute Plan",
226
175
  description:
227
- "Execution handoff for an accepted plan. Asks the user for explicit approval (never auto-completed), then asks which runtime executes the plan — the current session (recommended) or a delegated executor subagent on another model (>=3 switch targets listed; the child writes natively and reports [DONE:VC-xxx] markers the parent tracks). Either way the extension tracks Verifier-Checklist progress. When several runs with plans exist, a run-picker form selects the target run first. Only call after the user chose 'Execute this plan now' at the handoff question.",
176
+ "Execution handoff for an accepted plan. Asks the user for explicit approval (never auto-completed), then enters task-tree execution mode: every task's progress is reported via the plans_update_task tool (status + evidence), the task dashboard tracks the tree (Ctrl+Shift+T expands it), and an independent completion auditor verifies the verification checks before the run completes. Legacy I-### plans parse through the compatibility mapping with an upgrade notice. When several runs with plans exist, a run-picker form selects the target run first. Only call after the user chose 'Execute this plan now' at the handoff question.",
228
177
  promptSnippet: "Hand an accepted plan off to the tracked execution loop",
229
178
  parameters: ExecutePlanParams,
230
179
 
231
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
232
- const outcome = await executeHandoff(ctx, params.planPath, params.workdir, signal);
180
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
181
+ const outcome = await executeHandoff(ctx, params.planPath, params.workdir);
233
182
  if (outcome.status === "error") throw new Error(outcome.message);
234
183
  return {
235
184
  content: [{ type: "text", text: outcome.message }],
@@ -277,10 +277,6 @@ function createGraphReadTool(cwd: string) {
277
277
  };
278
278
  };
279
279
  const mode: GraphMode = resolveGraphMode(ctx.cwd);
280
- // Delegated executor children (PI_PLANS_EXECUTOR=1) always use the
281
- // native tools: DB-first staging would never be materialized inside
282
- // the child (no code_graph in its allowlist), so writes must hit disk.
283
- if (process.env.PI_PLANS_EXECUTOR === "1") return native(null);
284
280
  if (mode === "off") return native(null);
285
281
  if (mode === "config-unavailable") return native("config read failed");
286
282
  const ensured = await ensureRuntime(ctx.cwd, ctx);
@@ -331,7 +327,6 @@ function createGraphWriteTool(cwd: string) {
331
327
  };
332
328
  const mode: GraphMode = resolveGraphMode(ctx.cwd);
333
329
  // Delegated executor children bypass DB-first staging (see read tool).
334
- if (process.env.PI_PLANS_EXECUTOR === "1") return stage(null);
335
330
  if (mode === "off") return stage(null);
336
331
  if (mode === "config-unavailable") return stage("config read failed");
337
332
  const ensured = await ensureRuntime(ctx.cwd, ctx);
@@ -382,7 +377,6 @@ function createGraphEditTool(cwd: string) {
382
377
  };
383
378
  const mode: GraphMode = resolveGraphMode(ctx.cwd);
384
379
  // Delegated executor children bypass DB-first staging (see read tool).
385
- if (process.env.PI_PLANS_EXECUTOR === "1") return stage(null);
386
380
  if (mode === "off") return stage(null);
387
381
  if (mode === "config-unavailable") return stage("config read failed");
388
382
  const ensured = await ensureRuntime(ctx.cwd, ctx);
@@ -453,9 +447,9 @@ export function createGraphAwareFileTools(cwd: string) {
453
447
  return tools;
454
448
  }
455
449
 
456
- export function registerGraphAwareFileTools(pi: ExtensionAPI, cwd = process.cwd()): void {
450
+ export function registerGraphAwareFileTools(ext: ExtensionAPI, cwd = process.cwd()): void {
457
451
  const tools = createGraphAwareFileTools(cwd);
458
- pi.registerTool(tools.read);
459
- pi.registerTool(tools.write);
460
- pi.registerTool(tools.edit);
452
+ ext.registerTool(tools.read);
453
+ ext.registerTool(tools.write);
454
+ ext.registerTool(tools.edit);
461
455
  }
package/tools/plans.ts CHANGED
@@ -8,9 +8,6 @@ import { lintPlanIntoNotices } from "../src/state.ts";
8
8
  import { getExecution, markPrePlanCompactPending, refreshUiLanguage } from "../src/exec.ts";
9
9
  import { loadVccSettings, scaffoldVccSettings } from "../src/compaction.ts";
10
10
  import {
11
- applyCompleted,
12
- applyImplementationReviewConfigured,
13
- applyImplementationRoundFinished,
14
11
  applyPlanWritten,
15
12
  applyReviewConsolidated,
16
13
  createCheckpoint,
@@ -43,11 +40,12 @@ import {
43
40
  setRefsRoot,
44
41
  setRunStatus,
45
42
  setRole,
46
- showConfig,
43
+ showStateView,
47
44
  startRun,
48
45
  StateError,
49
46
  VALID_RUN_STATUSES,
50
47
  } from "../src/state.ts";
48
+ import { messaging } from "../src/messaging.ts";
51
49
 
52
50
  const PlansParams = Type.Object({
53
51
  action: StringEnum(
@@ -77,19 +75,25 @@ const PlansParams = Type.Object({
77
75
  requestText: Type.Optional(Type.String({ description: "start-run: original user request text" })),
78
76
  tag: Type.Optional(Type.String({ description: "set-language: BCP47 tag, e.g. zh-Hans, en" })),
79
77
  languageSource: Type.Optional(StringEnum(["user", "auto"] as const)),
80
- artifactRoot: Type.Optional(Type.String({ description: "set-artifact-root: planning docs root, e.g. ./docs/pi-plans" })),
78
+ artifactRoot: Type.Optional(Type.String({ description: "set-artifact-root: planning docs root, e.g. ./.git/pi-plans/plans" })),
81
79
  artifactRootSource: Type.Optional(StringEnum(["user", "auto"] as const)),
82
80
  refsRoot: Type.Optional(Type.String({ description: "set-refs-root: reference downloads root, e.g. .git/pi-plans/refs" })),
83
81
  refsRootSource: Type.Optional(StringEnum(["user", "auto"] as const)),
84
82
  enabled: Type.Optional(Type.Boolean({ description: "set-graph-enabled: enable/disable the code graph" })),
85
83
  message: Type.Optional(Type.String({ description: "final-commit: commit message body" })),
86
- role: Type.Optional(StringEnum(["reviewer", "criticizer"] as const)),
84
+ role: Type.Optional(StringEnum(["reviewer"] as const)),
87
85
  mode: Type.Optional(StringEnum(["delegated-subagent", "current-session"] as const)),
88
86
  modelSelector: Type.Optional(
89
- Type.String({ description: "set-role: exact provider/model selector, or 'inherit' to reset to inherited" }),
87
+ Type.String({ description: "set-role: exact provider/model selector; 'inherit' resets BOTH the selector and the confirmation" }),
88
+ ),
89
+ thinkingLevel: Type.Optional(
90
+ StringEnum(["default", "off", "minimal", "low", "medium", "high", "xhigh", "max"] as const, {
91
+ description:
92
+ "set-role: reviewer subagent thinking level in the GLOBAL config; 'default' (null) omits --thinking so the child pi resolves its own default chain; changing modelSelector without this resets the level",
93
+ }),
90
94
  ),
91
95
  confirmed: Type.Optional(
92
- Type.Boolean({ description: "set-role: stamp confirmed_at=now (used by the first-use confirmation flow)" }),
96
+ Type.Boolean({ description: "set-role: stamp confirmed_at=now; requires an exact selector for delegated-subagent" }),
93
97
  ),
94
98
  resetConfirmation: Type.Optional(Type.Boolean({ description: "set-role: clear confirmed_at to re-ask" })),
95
99
  runId: Type.Optional(Type.String()),
@@ -121,7 +125,7 @@ const PlansParams = Type.Object({
121
125
  ),
122
126
  subagent: Type.Optional(
123
127
  Type.Object({
124
- role: StringEnum(["reviewer", "criticizer", "ref-analyst"] as const),
128
+ role: StringEnum(["reviewer", "ref-analyst"] as const),
125
129
  name: Type.String(),
126
130
  model: Type.Optional(Type.String()),
127
131
  sessionDir: Type.Optional(Type.String()),
@@ -130,41 +134,16 @@ const PlansParams = Type.Object({
130
134
  checkpoint: Type.Optional(
131
135
  Type.Object({
132
136
  /** Whitelisted semantic transition (I-003). State-machine validated; approval cannot be forged here. */
133
- transition: StringEnum(
134
- [
135
- "plan-written",
136
- "review-consolidated",
137
- "implementation-review-configured",
138
- "implementation-round-finished",
139
- "completed",
140
- ] as const,
141
- ),
137
+ transition: StringEnum(["plan-written", "review-consolidated"] as const),
142
138
  /** plan-written: absolute or workdir-relative PLAN_vN.md path. */
143
139
  planPath: Type.Optional(Type.String()),
144
140
  /** review-consolidated: round id + optional disposition artifact (run-dir relative). */
145
141
  roundId: Type.Optional(Type.String()),
146
142
  dispositionArtifact: Type.Optional(Type.String()),
147
- /** implementation-review-configured: the serialized termination condition chosen by the user. */
148
- terminationCondition: Type.Optional(Type.String()),
149
- /** implementation-review-configured: concurrent reviewers per round (1-3);
150
- * omit to keep the skill-level default (plan-big / plan-with-refs → 3, others → 1). */
151
- reviewerCount: Type.Optional(Type.Integer({ minimum: 1, maximum: 3 })),
152
- /** completed: non-empty evidence that the termination condition is satisfied. */
153
- evidence: Type.Optional(Type.String()),
154
143
  }),
155
144
  ),
156
145
  });
157
146
 
158
- // Module-level reference so the start-run action can append a session entry
159
- // (pi-plans-run-start) at the moment the planning run begins. The ExtensionAPI
160
- // itself is not in scope for `startRun`, mirroring `setCurrentApi` in
161
- // tools/execute-plan.ts.
162
- let runStartAppender: ((runId: string, artifactDir: string) => void) | null = null;
163
-
164
- export function setRunStartAppender(appender: ((runId: string, artifactDir: string) => void) | null): void {
165
- runStartAppender = appender;
166
- }
167
-
168
147
  /** plans action final-commit: gate on code-graph drift (zero pending +
169
148
  * invariants (a)/(b) clean), then `git add -A` and commit the plan delivery.
170
149
  * A clean tree is a safe no-op. Returns a machine-readable result. */
@@ -238,13 +217,10 @@ export function recordCheckpointTransition(
238
217
  workdir: string,
239
218
  runIdArg: string | undefined,
240
219
  checkpoint: {
241
- transition: "plan-written" | "review-consolidated" | "implementation-review-configured" | "implementation-round-finished" | "completed";
220
+ transition: "plan-written" | "review-consolidated";
242
221
  planPath?: string;
243
222
  roundId?: string;
244
223
  dispositionArtifact?: string;
245
- terminationCondition?: string;
246
- reviewerCount?: number;
247
- evidence?: string;
248
224
  },
249
225
  ): WorkflowCheckpoint {
250
226
  const runId =
@@ -270,33 +246,15 @@ export function recordCheckpointTransition(
270
246
  applyReviewConsolidated(cp, checkpoint.roundId!, checkpoint.dispositionArtifact),
271
247
  );
272
248
  }
273
- case "implementation-review-configured": {
274
- if (!checkpoint.terminationCondition) {
275
- throw new StateError("implementation-review-configured requires terminationCondition");
276
- }
277
- return mutateCheckpoint(workdir, runId, (cp) =>
278
- applyImplementationReviewConfigured(cp, checkpoint.terminationCondition!, checkpoint.reviewerCount),
279
- );
280
- }
281
- case "implementation-round-finished": {
282
- return mutateCheckpoint(workdir, runId, (cp) => applyImplementationRoundFinished(cp));
283
- }
284
- case "completed": {
285
- if (!checkpoint.evidence) throw new StateError("completed requires evidence");
286
- return mutateCheckpoint(workdir, runId, (cp) => applyCompleted(cp, checkpoint.evidence!));
287
- }
288
249
  }
289
250
  }
290
251
 
291
- export function registerPlansTool(pi: ExtensionAPI): void {
292
- setRunStartAppender((runId, artifactDir) => {
293
- pi.appendEntry("pi-plans-run-start", { runId, artifactDir });
294
- });
295
- pi.registerTool({
252
+ export function registerPlansTool(ext: ExtensionAPI): void {
253
+ ext.registerTool({
296
254
  name: "plans",
297
255
  label: "Plans",
298
256
  description:
299
- "Manage pi-plans planning state in the target workspace: init/show config, set language and planning docs root plus reviewer/criticizer roles and the code-graph enabled flag, start planning runs, record decisions/refs/subagents, and update run status. Multiple concurrent runs per workdir are supported (registry-derived from runs/; sessions bind to their run). State lives in .git/pi_plans/ inside the resolved git common dir. Actions: init, show, set-language, set-artifact-root, set-refs-root, set-graph-enabled, set-role, start-run, set-status, final-commit, record-decision, record-ref, record-subagent.",
257
+ "Manage pi-plans planning state in the target workspace: init/show config, set language and planning docs root plus the reviewer role and the code-graph enabled flag, start planning runs, record decisions/refs/subagents, and update run status. Multiple concurrent runs per workdir are supported (registry-derived from runs/; sessions bind to their run). State lives in .git/pi-plans/ inside the resolved git common dir. Actions: init, show, set-language, set-artifact-root, set-refs-root, set-graph-enabled, set-role, start-run, set-status, final-commit, record-decision, record-ref, record-subagent.",
300
258
  promptSnippet: "Manage pi-plans planning state, runs, and ledgers",
301
259
  parameters: PlansParams,
302
260
 
@@ -317,10 +275,15 @@ export function registerPlansTool(pi: ExtensionAPI): void {
317
275
  break;
318
276
  }
319
277
  case "show": {
320
- const config = showConfig(workdir);
321
- const stateRoot = resolveStateRootOrNull(workdir);
322
- result = { config, stateRoot };
323
- if (config.graph_enabled === null) {
278
+ const view = showStateView(workdir);
279
+ result = {
280
+ config: view.config,
281
+ stateRoot: view.stateRoot,
282
+ reviewer: view.reviewer,
283
+ globalConfigPath: view.globalConfigPath,
284
+ notices: view.notices,
285
+ };
286
+ if (view.config.graph_enabled === null) {
324
287
  result = {
325
288
  ...(result as Record<string, unknown>),
326
289
  hint: "graph_enabled is null (never asked). Ask the user once via ask_choice, then persist with plans (action: set-graph-enabled, enabled: true|false).",
@@ -369,10 +332,17 @@ export function registerPlansTool(pi: ExtensionAPI): void {
369
332
  role: params.role,
370
333
  mode: params.mode,
371
334
  modelSelector: params.modelSelector,
335
+ thinkingLevel: params.thinkingLevel,
372
336
  confirmed: params.confirmed,
373
337
  resetConfirmation: params.resetConfirmation,
374
338
  });
375
- result = { config: updated.config, stateRoot: updated.stateRoot, notices: updated.notices };
339
+ result = {
340
+ global: updated.global,
341
+ globalRoot: updated.globalRoot,
342
+ notices: updated.notices,
343
+ config: updated.config,
344
+ stateRoot: updated.stateRoot,
345
+ };
376
346
  break;
377
347
  }
378
348
  case "start-run": {
@@ -383,8 +353,11 @@ export function registerPlansTool(pi: ExtensionAPI): void {
383
353
  topic: params.topic,
384
354
  skill: params.skill,
385
355
  requestText: params.requestText,
356
+ // Append the run-start session entry with this call's ctx —
357
+ // never a registration-time capture (stale after session
358
+ // replacement).
386
359
  onStart: (run) => {
387
- runStartAppender?.(run.run_id, run.artifact_dir);
360
+ messaging().appendEntry("pi-plans-run-start", { runId: run.run_id, artifactDir: run.artifact_dir });
388
361
  },
389
362
  });
390
363
  // I-002: attribute this session's work to the run it started.
@@ -454,3 +427,4 @@ export function registerPlansTool(pi: ExtensionAPI): void {
454
427
  },
455
428
  });
456
429
  }
430
+