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.
- package/CONTRIBUTING.md +3 -3
- package/README.md +39 -37
- package/agents/reviewer.md +12 -3
- package/index.ts +42 -35
- package/package.json +1 -1
- package/references/pi-planning-workflow.md +44 -60
- package/references/plan-artifact-template.md +71 -60
- package/references/state-and-config.md +59 -43
- package/scripts/bench/pi-adapter/pi_plans_bench.py +33 -22
- package/scripts/bench/pi-adapter/rpc_driver.mjs +4 -4
- package/scripts/run-tests.ts +12 -1
- package/scripts/validate.ts +20 -9
- package/skills/debug-and-plan/SKILL.md +3 -3
- package/skills/plan-big/SKILL.md +3 -3
- package/skills/plan-normal/SKILL.md +3 -3
- package/skills/plan-small/SKILL.md +4 -4
- package/skills/plan-with-refs/SKILL.md +6 -6
- package/skills/planning/SKILL.md +1 -1
- package/src/ask-form.ts +4 -4
- package/src/auditor.ts +126 -0
- package/src/auto-approve.ts +1 -1
- package/src/autocomplete.ts +19 -17
- package/src/code-graph/commands.ts +2 -2
- package/src/code-graph/community.ts +1 -1
- package/src/code-graph/paths.ts +1 -1
- package/src/code-graph/watch.ts +2 -2
- package/src/compaction.ts +3 -3
- package/src/config-command.ts +146 -73
- package/src/dashboard.ts +257 -0
- package/src/exec.ts +692 -919
- package/src/global-state.ts +304 -0
- package/src/guard.ts +18 -19
- package/src/messaging.ts +44 -0
- package/src/plan.ts +421 -112
- package/src/query-hook.ts +4 -4
- package/src/refine-prompts.ts +12 -70
- package/src/refine-ui-helpers.ts +24 -5
- package/src/refine-ui-state.ts +1 -1
- package/src/refine-ui.ts +1 -1
- package/src/resume-command.ts +34 -128
- package/src/role-panels.ts +542 -0
- package/src/run-context.ts +3 -10
- package/src/state.ts +272 -72
- package/src/subagent.ts +19 -29
- package/src/task-tool.ts +100 -0
- package/src/tasks.ts +189 -0
- package/src/thinking-levels.ts +67 -0
- package/src/ui-language.ts +3 -54
- package/src/workflow-state.ts +63 -58
- package/tests/analyze-refs.test.ts +35 -18
- package/tests/ask-choice-schema.test.ts +0 -12
- package/tests/ask-choice.test.ts +2 -49
- package/tests/ask-form-tool.test.ts +4 -5
- package/tests/ask-form.test.ts +2 -2
- package/tests/auditor.test.ts +111 -0
- package/tests/auto-approve.test.ts +7 -10
- package/tests/autocomplete.test.ts +8 -11
- package/tests/code-graph-apply-action.test.ts +2 -2
- package/tests/code-graph-commands.test.ts +2 -2
- package/tests/code-graph-index.test.ts +2 -2
- package/tests/code-graph-loop.e2e.test.ts +1 -1
- package/tests/code-graph-mutations.test.ts +1 -1
- package/tests/code-graph-rollback.test.ts +1 -1
- package/tests/code-graph-v05.test.ts +2 -2
- package/tests/compaction.test.ts +1 -1
- package/tests/config-command.test.ts +103 -100
- package/tests/dashboard.test.ts +268 -0
- package/tests/exec-lifecycle.test.ts +181 -115
- package/tests/exec-panel-lifecycle.test.ts +106 -251
- package/tests/exec.test.ts +617 -1706
- package/tests/execute-plan.test.ts +44 -19
- package/tests/extension-load.test.ts +48 -0
- package/tests/global-state.test.ts +371 -0
- package/tests/graph-aware-file-tools.test.ts +5 -5
- package/tests/guard.test.ts +1 -1
- package/tests/multi-run.test.ts +3 -103
- package/tests/plan.test.ts +139 -62
- package/tests/plans.test.ts +7 -79
- package/tests/refine-prompts.test.ts +20 -71
- package/tests/refine-resume.test.ts +27 -22
- package/tests/refine-ui.test.ts +6 -15
- package/tests/resume-lifecycle.test.ts +37 -22
- package/tests/resume.test.ts +33 -81
- package/tests/role-panels.test.ts +391 -0
- package/tests/run-context.test.ts +1 -1
- package/tests/run-ownership.test.ts +1 -1
- package/tests/stale-ctx.test.ts +218 -0
- package/tests/state.test.ts +151 -32
- package/tests/subagent-thinking.test.ts +65 -0
- package/tests/subagent-usage.test.ts +1 -1
- package/tests/task-tool.test.ts +61 -0
- package/tests/thinking-levels.test.ts +77 -0
- package/tests/ui-language.test.ts +2 -17
- package/tests/workflow-state.test.ts +17 -99
- package/tools/analyze-refs.ts +67 -32
- package/tools/ask-choice.ts +7 -53
- package/tools/code-graph.ts +2 -2
- package/tools/execute-plan.ts +48 -99
- package/tools/graph-aware-file-tools.ts +4 -10
- package/tools/plans.ts +40 -66
- package/tools/refine.ts +101 -164
- package/agents/criticizer.md +0 -18
- package/agents/executor.md +0 -26
- package/scripts/bench/pi-adapter/__pycache__/pi_plans_bench.cpython-312.pyc +0 -0
- package/src/panel.ts +0 -473
- package/src/termination-prompt.ts +0 -73
- package/tests/goal-wait.test.ts +0 -269
- package/tests/panel-i-zero.test.ts +0 -420
- package/tests/panel.test.ts +0 -355
package/tools/execute-plan.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `execute_plan` tool — the execution handoff. On explicit user
|
|
3
|
-
* Auto-complete) the extension enters execution mode
|
|
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,
|
|
20
|
-
import { normalizeWorkdir,
|
|
21
|
-
import { bindRun
|
|
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
|
-
|
|
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
|
|
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}
|
|
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
|
-
|
|
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
|
|
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. ${
|
|
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(
|
|
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;
|
|
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
|
-
|
|
211
|
-
|
|
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
|
|
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,
|
|
232
|
-
const outcome = await executeHandoff(ctx, params.planPath, params.workdir
|
|
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(
|
|
450
|
+
export function registerGraphAwareFileTools(ext: ExtensionAPI, cwd = process.cwd()): void {
|
|
457
451
|
const tools = createGraphAwareFileTools(cwd);
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|
|
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
|
|
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
|
|
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", "
|
|
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"
|
|
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(
|
|
292
|
-
|
|
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
|
|
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
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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 = {
|
|
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
|
-
|
|
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
|
+
|