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.
- package/CONTRIBUTING.md +126 -0
- package/README.md +49 -39
- package/agents/ref-analyst.md +7 -4
- package/agents/reviewer.md +12 -3
- package/index.ts +74 -40
- package/package.json +2 -1
- package/references/pi-planning-workflow.md +45 -58
- package/references/plan-artifact-template.md +71 -60
- package/references/state-and-config.md +63 -47
- 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 +22 -10
- package/skills/debug-and-plan/SKILL.md +4 -4
- package/skills/plan-big/SKILL.md +5 -5
- package/skills/plan-normal/SKILL.md +5 -5
- package/skills/plan-small/SKILL.md +5 -5
- package/skills/plan-with-refs/SKILL.md +8 -8
- 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 +154 -76
- package/src/dashboard.ts +257 -0
- package/src/exec.ts +709 -705
- package/src/global-state.ts +304 -0
- package/src/guard.ts +16 -3
- package/src/messaging.ts +44 -0
- package/src/plan.ts +421 -112
- package/src/query-hook.ts +4 -4
- package/src/refine-prompts.ts +14 -72
- 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 +40 -130
- package/src/resume.ts +15 -17
- package/src/role-panels.ts +542 -0
- package/src/run-context.ts +5 -4
- package/src/run-picker.ts +98 -0
- package/src/state.ts +380 -77
- package/src/subagent.ts +32 -1
- 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 +78 -57
- package/tests/analyze-refs.test.ts +35 -18
- package/tests/ask-choice-pros-cons.test.ts +147 -0
- 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 +184 -0
- 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 +43 -88
- 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 +19 -49
- package/tools/code-graph.ts +2 -2
- package/tools/execute-plan.ts +63 -33
- package/tools/graph-aware-file-tools.ts +6 -4
- package/tools/plans.ts +40 -66
- package/tools/refine.ts +101 -164
- package/agents/criticizer.md +0 -18
- 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";
|
|
@@ -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,
|
|
19
|
-
import { normalizeWorkdir,
|
|
20
|
-
import {
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
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(
|
|
68
|
+
const latest = latestPlanVersion(chosenRun.artifact_dir);
|
|
57
69
|
if (!latest) {
|
|
58
|
-
return { status: "error", message: `No PLAN_vN.md found in ${
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
103
|
-
|
|
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. ${
|
|
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(
|
|
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;
|
|
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
|
-
|
|
130
|
-
|
|
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
|
|
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(
|
|
450
|
+
export function registerGraphAwareFileTools(ext: ExtensionAPI, cwd = process.cwd()): void {
|
|
449
451
|
const tools = createGraphAwareFileTools(cwd);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
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
|
+
|