pi-plans 0.5.7 → 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 (112) hide show
  1. package/CONTRIBUTING.md +126 -0
  2. package/README.md +49 -39
  3. package/agents/ref-analyst.md +7 -4
  4. package/agents/reviewer.md +12 -3
  5. package/index.ts +74 -40
  6. package/package.json +2 -1
  7. package/references/pi-planning-workflow.md +45 -58
  8. package/references/plan-artifact-template.md +71 -60
  9. package/references/state-and-config.md +63 -47
  10. package/scripts/bench/pi-adapter/pi_plans_bench.py +33 -22
  11. package/scripts/bench/pi-adapter/rpc_driver.mjs +4 -4
  12. package/scripts/run-tests.ts +12 -1
  13. package/scripts/validate.ts +22 -10
  14. package/skills/debug-and-plan/SKILL.md +4 -4
  15. package/skills/plan-big/SKILL.md +5 -5
  16. package/skills/plan-normal/SKILL.md +5 -5
  17. package/skills/plan-small/SKILL.md +5 -5
  18. package/skills/plan-with-refs/SKILL.md +8 -8
  19. package/skills/planning/SKILL.md +1 -1
  20. package/src/ask-form.ts +4 -4
  21. package/src/auditor.ts +126 -0
  22. package/src/auto-approve.ts +1 -1
  23. package/src/autocomplete.ts +19 -17
  24. package/src/code-graph/commands.ts +2 -2
  25. package/src/code-graph/community.ts +1 -1
  26. package/src/code-graph/paths.ts +1 -1
  27. package/src/code-graph/watch.ts +2 -2
  28. package/src/compaction.ts +3 -3
  29. package/src/config-command.ts +154 -76
  30. package/src/dashboard.ts +257 -0
  31. package/src/exec.ts +709 -705
  32. package/src/global-state.ts +304 -0
  33. package/src/guard.ts +16 -3
  34. package/src/messaging.ts +44 -0
  35. package/src/plan.ts +421 -112
  36. package/src/query-hook.ts +4 -4
  37. package/src/refine-prompts.ts +14 -72
  38. package/src/refine-ui-helpers.ts +24 -5
  39. package/src/refine-ui-state.ts +1 -1
  40. package/src/refine-ui.ts +1 -1
  41. package/src/resume-command.ts +40 -130
  42. package/src/resume.ts +15 -17
  43. package/src/role-panels.ts +542 -0
  44. package/src/run-context.ts +5 -4
  45. package/src/run-picker.ts +98 -0
  46. package/src/state.ts +380 -77
  47. package/src/subagent.ts +32 -1
  48. package/src/task-tool.ts +100 -0
  49. package/src/tasks.ts +189 -0
  50. package/src/thinking-levels.ts +67 -0
  51. package/src/ui-language.ts +3 -54
  52. package/src/workflow-state.ts +78 -57
  53. package/tests/analyze-refs.test.ts +35 -18
  54. package/tests/ask-choice-pros-cons.test.ts +147 -0
  55. package/tests/ask-choice-schema.test.ts +0 -12
  56. package/tests/ask-choice.test.ts +2 -49
  57. package/tests/ask-form-tool.test.ts +4 -5
  58. package/tests/ask-form.test.ts +2 -2
  59. package/tests/auditor.test.ts +111 -0
  60. package/tests/auto-approve.test.ts +7 -10
  61. package/tests/autocomplete.test.ts +8 -11
  62. package/tests/code-graph-apply-action.test.ts +2 -2
  63. package/tests/code-graph-commands.test.ts +2 -2
  64. package/tests/code-graph-index.test.ts +2 -2
  65. package/tests/code-graph-loop.e2e.test.ts +1 -1
  66. package/tests/code-graph-mutations.test.ts +1 -1
  67. package/tests/code-graph-rollback.test.ts +1 -1
  68. package/tests/code-graph-v05.test.ts +2 -2
  69. package/tests/compaction.test.ts +1 -1
  70. package/tests/config-command.test.ts +103 -100
  71. package/tests/dashboard.test.ts +268 -0
  72. package/tests/exec-lifecycle.test.ts +181 -115
  73. package/tests/exec-panel-lifecycle.test.ts +106 -251
  74. package/tests/exec.test.ts +617 -1706
  75. package/tests/execute-plan.test.ts +44 -19
  76. package/tests/extension-load.test.ts +48 -0
  77. package/tests/global-state.test.ts +371 -0
  78. package/tests/graph-aware-file-tools.test.ts +5 -5
  79. package/tests/guard.test.ts +1 -1
  80. package/tests/multi-run.test.ts +184 -0
  81. package/tests/plan.test.ts +139 -62
  82. package/tests/plans.test.ts +7 -79
  83. package/tests/refine-prompts.test.ts +20 -71
  84. package/tests/refine-resume.test.ts +27 -22
  85. package/tests/refine-ui.test.ts +6 -15
  86. package/tests/resume-lifecycle.test.ts +37 -22
  87. package/tests/resume.test.ts +43 -88
  88. package/tests/role-panels.test.ts +391 -0
  89. package/tests/run-context.test.ts +1 -1
  90. package/tests/run-ownership.test.ts +1 -1
  91. package/tests/stale-ctx.test.ts +218 -0
  92. package/tests/state.test.ts +151 -32
  93. package/tests/subagent-thinking.test.ts +65 -0
  94. package/tests/subagent-usage.test.ts +1 -1
  95. package/tests/task-tool.test.ts +61 -0
  96. package/tests/thinking-levels.test.ts +77 -0
  97. package/tests/ui-language.test.ts +2 -17
  98. package/tests/workflow-state.test.ts +17 -99
  99. package/tools/analyze-refs.ts +67 -32
  100. package/tools/ask-choice.ts +19 -49
  101. package/tools/code-graph.ts +2 -2
  102. package/tools/execute-plan.ts +63 -33
  103. package/tools/graph-aware-file-tools.ts +6 -4
  104. package/tools/plans.ts +40 -66
  105. package/tools/refine.ts +101 -164
  106. package/agents/criticizer.md +0 -18
  107. package/scripts/bench/pi-adapter/__pycache__/pi_plans_bench.cpython-312.pyc +0 -0
  108. package/src/panel.ts +0 -473
  109. package/src/termination-prompt.ts +0 -73
  110. package/tests/goal-wait.test.ts +0 -269
  111. package/tests/panel-i-zero.test.ts +0 -420
  112. 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";
@@ -15,9 +18,11 @@ import {
15
18
  } from "../src/exec.ts";
16
19
  import { disableAutoComplete } from "../src/autocomplete.ts";
17
20
  import { isAutoApproveEnabled } from "../src/auto-approve.ts";
18
- import { latestPlanVersion, parseChecklist, parseImplItems } from "../src/plan.ts";
19
- import { normalizeWorkdir, readActive } from "../src/state.ts";
20
- import { 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";
24
+ import { executionCandidates, resolveCommandRun } from "../src/run-picker.ts";
25
+ import { resolveUiLanguage } from "../src/ui-language.ts";
21
26
 
22
27
 
23
28
  const ExecutePlanParams = Type.Object({
@@ -39,23 +44,30 @@ export async function executeHandoff(
39
44
  ctx: ExtensionContext,
40
45
  planPathArg?: string,
41
46
  workdirArg?: string,
47
+ _signal?: AbortSignal,
42
48
  ): Promise<HandoffOutcome> {
43
49
  const workdir = normalizeWorkdir(workdirArg ?? ctx.cwd);
44
50
 
45
51
  let planPath: string | null = null;
52
+ let chosenRun: RunSummary | null = null;
46
53
  if (planPathArg) {
47
54
  planPath = path.resolve(workdir, planPathArg.replace(/^@/, ""));
48
55
  } else {
49
- const active = resolveActiveRun(ctx.sessionManager, workdir);
50
- if (!active) {
56
+ // v0.6.0 (R-3): pick the run explicitly when several execution
57
+ // candidates coexist; binding-first, single candidate stays direct.
58
+ chosenRun = await resolveCommandRun(
59
+ { cwd: workdir, sessionManager: ctx.sessionManager, ui: ctx.ui },
60
+ { candidates: executionCandidates(workdir), title: "Execute which run?" },
61
+ );
62
+ if (!chosenRun) {
51
63
  return {
52
64
  status: "error",
53
- message: "No plan path given and no active planning run found. Pass planPath or start a run first.",
65
+ message: "No plan path given and no executable planning run found. Pass planPath or start a run first.",
54
66
  };
55
67
  }
56
- const latest = latestPlanVersion(active.artifact_dir);
68
+ const latest = latestPlanVersion(chosenRun.artifact_dir);
57
69
  if (!latest) {
58
- return { status: "error", message: `No PLAN_vN.md found in ${active.artifact_dir}` };
70
+ return { status: "error", message: `No PLAN_vN.md found in ${chosenRun.artifact_dir}` };
59
71
  }
60
72
  planPath = latest.path;
61
73
  }
@@ -68,10 +80,28 @@ export async function executeHandoff(
68
80
  if (items.length === 0) {
69
81
  return {
70
82
  status: "error",
71
- 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.`,
84
+ };
85
+ }
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\`).`,
72
91
  };
73
92
  }
74
- const implItems = parseImplItems(planText);
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
+ }
75
105
 
76
106
  disableAutoComplete(ctx, "execution handoff");
77
107
  // I-004/D-019: PI_PLANS_AUTO_APPROVE=1 short-circuits the confirm BEFORE
@@ -85,28 +115,40 @@ export async function executeHandoff(
85
115
  };
86
116
  }
87
117
 
118
+ const legacyPlan = planTasks.legacy || checklistHeaderName(planText) === "Verifier Checklist";
119
+
88
120
  let approved: boolean;
89
121
  if (autoApprove) {
90
122
  approved = true;
91
123
  } else {
124
+ const lang = resolveUiLanguage(workdir);
92
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
+ : "";
93
131
  approved = await ctx.ui.confirm(
94
132
  "Execute this plan now?",
95
- `${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.`,
96
134
  );
97
135
  }
98
136
  if (!approved) {
99
137
  return { status: "declined", message: "User declined execution. Stay in planning; ask how to proceed.", planPath };
100
138
  }
101
139
 
102
- await startExecution(getCurrentApi(), ctx, planPath, items, implItems);
103
- const scopeNote = implItems.length ? ` Tracking ${implItems.length} implementation item(s).` : "";
140
+ // Attribute the handoff to the picked run before any state transition so
141
+ // the approval checkpoint and status flip land on the run the user chose.
142
+ if (chosenRun) bindRun(ctx.sessionManager, workdir, chosenRun.run_id);
143
+
144
+ await startExecution(ctx, { planPath, planTasks, items });
104
145
  const autoNote = autoApprove ? "[auto-approve] " : "";
146
+ const legacyNote = legacyPlan ? " Legacy I-### mapping active; upgrade the plan at the next revision." : "";
105
147
  return {
106
148
  status: "executing",
107
149
  planPath,
108
150
  itemCount: items.length,
109
- message: `${autoNote}Execution approved. ${items.length} verifier item(s) queued; implement in dependency order and mark verified items with [DONE:VC-xxx].${scopeNote}`,
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}`,
110
152
  };
111
153
  }
112
154
 
@@ -115,35 +157,23 @@ export async function executeCommand(ctx: ExtensionContext, planPathArg?: string
115
157
  const activeExecution = getExecution();
116
158
  const planPath = planPathArg ? path.resolve(ctx.cwd, planPathArg.replace(/^@/, "")) : activeExecution?.planPath;
117
159
  if (activeExecution && planPath && path.resolve(activeExecution.planPath) === path.resolve(planPath)) {
118
- const resumed = resumeActiveExecution(getCurrentApi(), ctx);
160
+ const resumed = resumeActiveExecution(ctx);
119
161
  return {
120
162
  status: "executing",
121
163
  planPath,
122
164
  itemCount: activeExecution.items.length,
123
- 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.",
124
166
  };
125
167
  }
126
168
  return executeHandoff(ctx, planPathArg);
127
169
  }
128
170
 
129
- // The tool registers with the ExtensionAPI in scope; keep a module-level
130
- // reference so the shared handoff helper can reach appendEntry/sendMessage.
131
- let currentApi: ExtensionAPI | null = null;
132
- export function setCurrentApi(api: ExtensionAPI): void {
133
- currentApi = api;
134
- }
135
- function getCurrentApi(): ExtensionAPI {
136
- if (!currentApi) throw new Error("execute_plan used before extension initialization");
137
- return currentApi;
138
- }
139
-
140
- export function registerExecutePlanTool(pi: ExtensionAPI): void {
141
- setCurrentApi(pi);
142
- pi.registerTool({
171
+ export function registerExecutePlanTool(ext: ExtensionAPI): void {
172
+ ext.registerTool({
143
173
  name: "execute_plan",
144
174
  label: "Execute Plan",
145
175
  description:
146
- "Execution handoff for an accepted plan. Asks the user for explicit approval (never auto-completed), then enters plan-execution mode: the extension injects the remaining Verifier Checklist every turn, tracks [DONE:VC-xxx] markers, and completes when every item passes. 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.",
147
177
  promptSnippet: "Hand an accepted plan off to the tracked execution loop",
148
178
  parameters: ExecutePlanParams,
149
179
 
@@ -326,6 +326,7 @@ function createGraphWriteTool(cwd: string) {
326
326
  };
327
327
  };
328
328
  const mode: GraphMode = resolveGraphMode(ctx.cwd);
329
+ // Delegated executor children bypass DB-first staging (see read tool).
329
330
  if (mode === "off") return stage(null);
330
331
  if (mode === "config-unavailable") return stage("config read failed");
331
332
  const ensured = await ensureRuntime(ctx.cwd, ctx);
@@ -375,6 +376,7 @@ function createGraphEditTool(cwd: string) {
375
376
  };
376
377
  };
377
378
  const mode: GraphMode = resolveGraphMode(ctx.cwd);
379
+ // Delegated executor children bypass DB-first staging (see read tool).
378
380
  if (mode === "off") return stage(null);
379
381
  if (mode === "config-unavailable") return stage("config read failed");
380
382
  const ensured = await ensureRuntime(ctx.cwd, ctx);
@@ -445,9 +447,9 @@ export function createGraphAwareFileTools(cwd: string) {
445
447
  return tools;
446
448
  }
447
449
 
448
- export function registerGraphAwareFileTools(pi: ExtensionAPI, cwd = process.cwd()): void {
450
+ export function registerGraphAwareFileTools(ext: ExtensionAPI, cwd = process.cwd()): void {
449
451
  const tools = createGraphAwareFileTools(cwd);
450
- pi.registerTool(tools.read);
451
- pi.registerTool(tools.write);
452
- pi.registerTool(tools.edit);
452
+ ext.registerTool(tools.read);
453
+ ext.registerTool(tools.write);
454
+ ext.registerTool(tools.edit);
453
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. 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
+