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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plan-with-refs
|
|
3
|
-
description: Research references before creating a Pi plan. Use when repo-change planning needs downloaded projects, articles, papers, docs, per-reference analysis, adoption questions, language settings, and reviewer
|
|
3
|
+
description: Research references before creating a Pi plan. Use when repo-change planning needs downloaded projects, articles, papers, docs, per-reference analysis, adoption questions, language settings, and reviewer refinement (findings plus questions); exclude direct implementation-only, factual/explanation, trivial command-only, or explicit no-plan requests.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plan With Refs
|
|
@@ -9,22 +9,22 @@ Use this skill when external references must shape the plan before implementatio
|
|
|
9
9
|
|
|
10
10
|
## Pi Setup
|
|
11
11
|
|
|
12
|
-
Read `../../references/pi-planning-workflow.md` and `../../references/state-and-config.md` — both normative — and follow their setup, state, `language`, and reviewer
|
|
12
|
+
Read `../../references/pi-planning-workflow.md` and `../../references/state-and-config.md` — both normative — and follow their setup, state, `language`, and reviewer rules. Initialize workspace state with the `plans` tool (`action: "init"`); state lives in `.git/pi-plans/`. Ask every question with the `ask_choice` tool (batch related questions and adoption questions into one `questions: [...]` form call, 2-8 items; scope/handoff stays single-question with `autoComplete: false`); run refinement rounds with the `refine` tool.
|
|
13
13
|
|
|
14
14
|
## Required Reference Flow
|
|
15
15
|
|
|
16
16
|
1. Inspect the target Git repo read-only before external research so search terms match the actual codebase and constraints.
|
|
17
|
-
2. Create the `.git/
|
|
17
|
+
2. Create the `.git/pi-plans` run state and planning artifact directory once the topic is clear (`plans` action `start-run`).
|
|
18
18
|
3. Search proactively for related projects, articles, papers, docs, and prior art. Prefer a websearch skill when installed; otherwise use bash tools such as `curl` or `gh` when already available.
|
|
19
|
-
4. Before the first download, check `refs_root` in `.git/
|
|
19
|
+
4. Before the first download, check `refs_root` in `.git/pi-plans/config.json` (`plans` action `show`). If it is unset, ask exactly one `ask_choice` question — recommended `.git/pi-plans/refs/` (inside the git dir, never tracked), second `./refs/`, third `~/.cache/pi-plans/refs/` — with each option's `description` set to `✓ <advantage> / ✗ <drawback>` in the configured language, kept terse, and persist with `plans` (`set-refs-root`); this question does not count against the planning-question limit. Then download at least 3 credible references across at least 2 distinct origins before writing `PLAN_v1.md`, under the configured refs root (one subdirectory per reference — `analyze_refs` requires directories). References are NOT limited to GitHub repositories: papers (e.g. arXiv), engineering blog posts, and documentation sites are first-class, and theoretical references count exactly as much as implementation references. A download is qualified per medium: repo = clone (full working tree); paper = the full text (arXiv HTML preferred, else the PDF with extracted text into `paper.txt`/`paper.md`; an abstract alone never qualifies); blog/docs site = the full-article readable markdown saved locally (single-page posts are fine — they ARE the source; only fragments or teasers fail). Landing pages, README-only snapshots, abstracts, package metadata, or curl-only fragments do not count when deeper source material is available.
|
|
20
20
|
5. For every reference, record source metadata and local path in `REF_ANALYSIS.md` and in the run's `refs.jsonl` (via `plans` action `record-ref`): title, URL, kind (`project` for repos, `paper` for papers, `article` for blog posts, `docs` for documentation sites), retrieval method, date accessed, local path, coverage, and evidence gaps.
|
|
21
21
|
6. For every reference, run the `analyze_refs` tool (required path — it replaces manual structured reads): one independent read-only subagent per reference deep-reads it and returns structured sections (Overview / Key Mechanisms And Design Tradeoffs / Adoptable Ideas For The Target Repo / Pitfalls And Anti-Patterns / Evidence Citations / Coverage / Evidence Gaps). Paste each analysis into `REF_ANALYSIS.md` and fill `coverage` and `gaps` in `refs.jsonl` via `plans` (`record-ref`) before asking adoption questions.
|
|
22
22
|
7. For every reference after analysis, ask at least 3 ref-specific adoption questions via `ask_choice` before using its ideas in `PLAN_v1.md`; each based on downloaded content, recommended option first, `Other` second-last, `Auto-complete` last (the tool appends both). Every option you write carries a `description` of `✓ <advantage> / ✗ <drawback>` in the configured language, kept terse — adoption choices trade a real gain against a real cost, so state both. Batch one reference's adoption questions into a single `questions: [...]` form call.
|
|
23
23
|
8. Block rather than pad if fewer than 3 credible references exist, unless the user explicitly narrows the topic or waives the minimum. `Auto-complete` cannot grant this waiver.
|
|
24
|
-
9. Continue with big-plan depth: at least 10 planning questions, required web research during brainstorming and refinement (`refine` `reviewers: 3` reviewer round
|
|
24
|
+
9. Continue with big-plan depth: at least 10 planning questions, required web research during brainstorming and refinement (`refine` `reviewers: 3` reviewer round carrying findings and questions), no refinement limit, at most five high-priority comments or questions per refinement round. Then the merged accept/execute question (ask_choice with `autoComplete: false`: ✓ Accept & execute now / Accept, don't execute yet / another round) and the `execute_plan` tool.
|
|
25
25
|
|
|
26
26
|
## REF_ANALYSIS.md
|
|
27
27
|
|
|
28
|
-
Include: original request and repo evidence that shaped the search; attempted queries and selection criteria; references selected and rejected; configured refs root and local download paths; the per-reference `analyze_refs` structured analyses (pasted verbatim, one section per reference); adoption questions and recorded answers; accepted ideas, rejected ideas, and reasons; evidence gaps and user-granted waivers; language, reviewer, and
|
|
28
|
+
Include: original request and repo evidence that shaped the search; attempted queries and selection criteria; references selected and rejected; configured refs root and local download paths; the per-reference `analyze_refs` structured analyses (pasted verbatim, one section per reference); adoption questions and recorded answers; accepted ideas, rejected ideas, and reasons; evidence gaps and user-granted waivers; language, reviewer, and reviewer settings used.
|
|
29
29
|
|
|
30
30
|
Reference ideas are not eligible for `PLAN_v1.md` until their adoption question answers are recorded.
|
package/skills/planning/SKILL.md
CHANGED
|
@@ -19,4 +19,4 @@ Use this skill when a task is planning-related but the right specialist is not o
|
|
|
19
19
|
|
|
20
20
|
## Pi Setup
|
|
21
21
|
|
|
22
|
-
Use the same language, `ask_choice`, `refine`, reviewer,
|
|
22
|
+
Use the same language, `ask_choice`, `refine`, reviewer, Auto-complete, and `.git/pi-plans` rules as the selected specialist skill and the shared workflow — including the per-option `✓ <advantage> / ✗ <drawback>` descriptions, which every option you write carries in the configured language, kept terse. Questions prefer the 0.4.0 batch form: one `ask_choice` call with `questions: [...]` (2-8) opens a tabbed multiple-choice form; scope confirmation and execution handoff stay single-question with `autoComplete: false`.
|
package/src/ask-form.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The form is a tabbed dialog opened via ctx.ui.custom: one tab per question
|
|
5
5
|
* (options always visible in the first frame — options-first fit contract from
|
|
6
|
-
*
|
|
6
|
+
* a guided multi-question questionnaire) plus a final submit page listing every Q/A, and a
|
|
7
7
|
* custom-answer row per tab that switches into a Focusable single-line input
|
|
8
8
|
* (CURSOR_MARKER + hardware cursor so zh-Hans IME composition works).
|
|
9
9
|
*
|
|
@@ -69,7 +69,7 @@ export interface FormState {
|
|
|
69
69
|
custom: (string | null)[];
|
|
70
70
|
/** Whether the user explicitly confirmed an answer on tab i (Enter on an
|
|
71
71
|
* option row or a committed custom answer). The cursor `selection` alone
|
|
72
|
-
* never flips this —
|
|
72
|
+
* never flips this — the ■/□ chips read this field. */
|
|
73
73
|
confirmed: boolean[];
|
|
74
74
|
/** Current tab: 0..N-1 = question tabs, N = submit page. */
|
|
75
75
|
tab: number;
|
|
@@ -86,7 +86,7 @@ export function createFormState(questions: FormQuestion[], lang: UiLanguage = "e
|
|
|
86
86
|
// Cursor position per tab: pre-positioned on the recommended option
|
|
87
87
|
// where present. This is ONLY the highlight — an answer counts only
|
|
88
88
|
// after the user confirms it (Enter/custom submit), tracked in
|
|
89
|
-
// `confirmed`
|
|
89
|
+
// `confirmed` semantics: chips show □ until answered.
|
|
90
90
|
selection: questions.map((q) => q.options.findIndex((o) => o.recommended === true)),
|
|
91
91
|
confirmed: questions.map(() => false),
|
|
92
92
|
custom: questions.map(() => null),
|
|
@@ -286,7 +286,7 @@ export const FORM_ROWS_RESERVE = 4;
|
|
|
286
286
|
export const FORM_FALLBACK_ROWS = 30;
|
|
287
287
|
|
|
288
288
|
/**
|
|
289
|
-
*
|
|
289
|
+
* themed question-tab frame: accent borders, tab chips with a
|
|
290
290
|
* selectedBg background on the active chip (■ answered / □ pending), colored
|
|
291
291
|
* question line, dim key hints. Under a `rows` budget it degrades richest
|
|
292
292
|
* first — descriptions, then blank separators, then the borders (the chips
|
package/src/auditor.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completion auditor (v0.6.1): when every task reaches a terminal state, an
|
|
3
|
+
* independent read-only subagent verifies the plan's verification checks
|
|
4
|
+
* against the worktree. Failed checks roll their covered tasks back to
|
|
5
|
+
* pending (exclusively inside the audit flow); three failed rounds pause the
|
|
6
|
+
* run for the user — or terminate it as stopped under auto-approve/headless
|
|
7
|
+
* so pipelines never hang.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import { auditRollbackSet, auditableChecks, skippedPassCheckIds, type TaskView } from "./tasks.ts";
|
|
13
|
+
import type { CheckItem } from "./plan.ts";
|
|
14
|
+
import { messaging } from "./messaging.ts";
|
|
15
|
+
|
|
16
|
+
export const AUDIT_MAX_ROUNDS = 3;
|
|
17
|
+
|
|
18
|
+
export interface AuditOutcome {
|
|
19
|
+
round: number;
|
|
20
|
+
passed: string[];
|
|
21
|
+
failed: string[];
|
|
22
|
+
/** Rolled-back task ids (empty when the audit passed). */
|
|
23
|
+
rolledBack: string[];
|
|
24
|
+
report: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Build the audit brief for the read-only subagent. Exported for tests. */
|
|
28
|
+
export function buildAuditTask(planPath: string, checklist: CheckItem[], tasks: TaskView[], round: number): string {
|
|
29
|
+
const checks = auditableChecks(checklist, tasks)
|
|
30
|
+
.map((check) => `- \`${check.id}\`: ${check.text}`)
|
|
31
|
+
.join("\n");
|
|
32
|
+
return `Goal: verify that the implemented worktree satisfies the accepted plan's verification checks.
|
|
33
|
+
|
|
34
|
+
Target plan: ${planPath} (audit round ${round})
|
|
35
|
+
|
|
36
|
+
Authority boundary: read-only analysis only. Do not edit, write, delete, commit, push, or spawn subagents.
|
|
37
|
+
|
|
38
|
+
Evidence: inspect the repository with read, grep, find, ls, and targeted commands (bash is not granted — rely on the read tools) before judging each check. Tests may be referenced from their recorded evidence; do not re-run them.
|
|
39
|
+
|
|
40
|
+
Checks to verify (only these; checks covering no task are excluded):
|
|
41
|
+
|
|
42
|
+
${checks}
|
|
43
|
+
|
|
44
|
+
Output: Markdown with exactly one section per check, in checklist order:
|
|
45
|
+
|
|
46
|
+
- \`VC-###\` — verdict: pass | fail; evidence: <repo path/command proving it>; note: <one line>.
|
|
47
|
+
|
|
48
|
+
Every check needs a verdict backed by evidence you actually inspected. If the evidence is inconclusive, verdict is fail with what is missing.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Parse the audit subagent's verdict lines. Exported for tests. */
|
|
52
|
+
export function parseAuditReport(report: string, checklist: CheckItem[]): { passed: string[]; failed: string[] } {
|
|
53
|
+
const passed: string[] = [];
|
|
54
|
+
const failed: string[] = [];
|
|
55
|
+
const known = new Set(checklist.map((item) => item.id));
|
|
56
|
+
for (const match of report.matchAll(/`?(VC-\d+)`?[^\n]*?verdict:\s*(pass|fail)/gi)) {
|
|
57
|
+
const id = match[1].toUpperCase();
|
|
58
|
+
if (!known.has(id)) continue;
|
|
59
|
+
(match[2].toLowerCase() === "pass" ? passed : failed).push(id);
|
|
60
|
+
}
|
|
61
|
+
// Dedupe, keep first occurrence order; a check with conflicting verdicts fails.
|
|
62
|
+
for (const id of [...passed]) if (failed.includes(id)) passed.splice(passed.indexOf(id), 1);
|
|
63
|
+
return { passed: [...new Set(passed)], failed: [...new Set(failed)] };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Pure decision core: given a parsed report, mutate the task tree with the
|
|
67
|
+
* rollback set. Returns the outcome (rolled-back ids). Exported for tests. */
|
|
68
|
+
export function applyAuditOutcome(
|
|
69
|
+
checklist: CheckItem[],
|
|
70
|
+
tasks: TaskView[],
|
|
71
|
+
round: number,
|
|
72
|
+
passed: string[],
|
|
73
|
+
failed: string[],
|
|
74
|
+
report: string,
|
|
75
|
+
): AuditOutcome {
|
|
76
|
+
for (const id of passed) {
|
|
77
|
+
const item = checklist.find((candidate) => candidate.id === id);
|
|
78
|
+
if (item) item.done = true;
|
|
79
|
+
}
|
|
80
|
+
const rolledBack: string[] = [];
|
|
81
|
+
for (const id of failed) {
|
|
82
|
+
rolledBack.push(...auditRollbackSet(tasks, checklist, id));
|
|
83
|
+
}
|
|
84
|
+
return { round, passed, failed, rolledBack, report };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Skipped-pass checks (all covered tasks skipped) pass without audit. */
|
|
88
|
+
export function presolvedCheckIds(checklist: CheckItem[], tasks: TaskView[]): string[] {
|
|
89
|
+
return skippedPassCheckIds(checklist, tasks);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Spawn the audit subagent and apply its outcome. Returns null when the
|
|
93
|
+
* audit subagent itself failed to run (treated as a failed round with an
|
|
94
|
+
* empty rollback; the caller counts it against the round cap). */
|
|
95
|
+
export async function runCompletionAudit(
|
|
96
|
+
ctx: ExtensionContext,
|
|
97
|
+
opts: {
|
|
98
|
+
planPath: string;
|
|
99
|
+
checklist: CheckItem[];
|
|
100
|
+
tasks: TaskView[];
|
|
101
|
+
round: number;
|
|
102
|
+
model?: string;
|
|
103
|
+
signal?: AbortSignal;
|
|
104
|
+
},
|
|
105
|
+
): Promise<AuditOutcome | null> {
|
|
106
|
+
const { runPiSubagent } = await import("./subagent.ts");
|
|
107
|
+
const task = buildAuditTask(opts.planPath, opts.checklist, opts.tasks, opts.round);
|
|
108
|
+
let agentPrompt = "You are a read-only completion auditor for pi-plans.";
|
|
109
|
+
try {
|
|
110
|
+
agentPrompt = fs.readFileSync(new URL("../agents/reviewer.md", import.meta.url), "utf8");
|
|
111
|
+
} catch {
|
|
112
|
+
/* fall back to the inline prompt */
|
|
113
|
+
}
|
|
114
|
+
const result = await runPiSubagent({
|
|
115
|
+
systemPrompt: `${agentPrompt}\n\nYou are acting as the completion auditor; the task brief below defines the output contract.`,
|
|
116
|
+
task,
|
|
117
|
+
cwd: ctx.cwd,
|
|
118
|
+
model: opts.model,
|
|
119
|
+
tools: ["read", "grep", "find", "ls"],
|
|
120
|
+
signal: opts.signal,
|
|
121
|
+
});
|
|
122
|
+
if (!result.ok) return null;
|
|
123
|
+
const { passed, failed } = parseAuditReport(result.output, opts.checklist);
|
|
124
|
+
messaging().appendEntry("pi-plans-audit", { planPath: opts.planPath, round: opts.round, passed, failed });
|
|
125
|
+
return applyAuditOutcome(opts.checklist, opts.tasks, opts.round, passed, failed, result.output);
|
|
126
|
+
}
|
package/src/auto-approve.ts
CHANGED
|
@@ -28,7 +28,7 @@ export function isAutoApproveEnabled(): boolean {
|
|
|
28
28
|
* must answer interactively) while a false negative would silently bypass a
|
|
29
29
|
* safety gate. Word-boundary anchored where short words could over-match.
|
|
30
30
|
* NOTE: package *installation inside a disposable benchmark container* is
|
|
31
|
-
* plan-lifecycle (the
|
|
31
|
+
* plan-lifecycle (the reviewer legitimately asks about installing deps);
|
|
32
32
|
* only externally-visible state (publish/deploy/merge/push/credentials/...)
|
|
33
33
|
* is hard-rejected. Install-permission waivers are still caught via
|
|
34
34
|
* "waiver". */
|
package/src/autocomplete.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { getRun, readActive } from "./state.ts";
|
|
5
5
|
import { resolveActiveRun } from "./run-context.ts";
|
|
6
|
+
import { messaging, tryMessaging } from "./messaging.ts";
|
|
6
7
|
|
|
7
8
|
export const AUTOCOMPLETE_ENTRY = "pi-plans-autocomplete";
|
|
8
9
|
const AUTOCOMPLETE_CONTINUE = "Continue the current planning workflow. Raise the next relevant question with ask_choice; do not stop after an auto-completed answer.";
|
|
@@ -22,12 +23,6 @@ type SessionWithAutoComplete = ExtensionContext["sessionManager"] & {
|
|
|
22
23
|
__piPlansAutoComplete?: AutoCompleteState;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
let api: ExtensionAPI | null = null;
|
|
26
|
-
|
|
27
|
-
export function setAutoCompleteApi(next: ExtensionAPI | null): void {
|
|
28
|
-
api = next;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
26
|
function sessionState(ctx: ExtensionContext): AutoCompleteState | undefined {
|
|
32
27
|
return (ctx.sessionManager as SessionWithAutoComplete).__piPlansAutoComplete;
|
|
33
28
|
}
|
|
@@ -39,9 +34,17 @@ function activePlanningRun(ctx: ExtensionContext): { runId: string } | null {
|
|
|
39
34
|
return run?.status === "planning" ? { runId: run.run_id } : null;
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
function appendState(runId: string, enabled: boolean, reason?: string): void {
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
function appendState(ctx: ExtensionContext, runId: string, enabled: boolean, reason?: string): void {
|
|
38
|
+
// Best-effort persistence: the in-memory session state is authoritative for
|
|
39
|
+
// the live turn; never let an unwired (tests) or stale (session swap) messaging
|
|
40
|
+
// surface break the ask_choice / handoff flows that call this.
|
|
41
|
+
const m = tryMessaging();
|
|
42
|
+
if (!m) return;
|
|
43
|
+
try {
|
|
44
|
+
m.appendEntry(AUTOCOMPLETE_ENTRY, { runId, enabled, ...(reason ? { reason } : {}) });
|
|
45
|
+
} catch {
|
|
46
|
+
/* session swap in flight; the entry is optional */
|
|
47
|
+
}
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export function enableAutoComplete(ctx: ExtensionContext): boolean {
|
|
@@ -57,7 +60,7 @@ export function enableAutoComplete(ctx: ExtensionContext): boolean {
|
|
|
57
60
|
autoChoiceCount: 0,
|
|
58
61
|
planWritten: false,
|
|
59
62
|
};
|
|
60
|
-
appendState(run.runId, true);
|
|
63
|
+
appendState(ctx, run.runId, true);
|
|
61
64
|
return true;
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -67,7 +70,7 @@ export function disableAutoComplete(ctx: ExtensionContext, reason = "disabled"):
|
|
|
67
70
|
const runId = state?.runId ?? run?.runId;
|
|
68
71
|
if (!runId && !state?.enabled) return false;
|
|
69
72
|
if (state) state.enabled = false;
|
|
70
|
-
if (runId) appendState(runId, false, reason);
|
|
73
|
+
if (runId) appendState(ctx, runId, false, reason);
|
|
71
74
|
return true;
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -110,12 +113,12 @@ export function shouldContinueAutoComplete(ctx: ExtensionContext): boolean {
|
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
export async function continueAutoComplete(ctx: ExtensionContext): Promise<boolean> {
|
|
113
|
-
if (!
|
|
116
|
+
if (!shouldContinueAutoComplete(ctx)) return false;
|
|
114
117
|
const state = sessionState(ctx);
|
|
115
118
|
if (!state) return false;
|
|
116
119
|
state.pendingFollowUp = true;
|
|
117
120
|
try {
|
|
118
|
-
await
|
|
121
|
+
await messaging().sendUserMessage(AUTOCOMPLETE_CONTINUE, { deliverAs: "followUp" });
|
|
119
122
|
return true;
|
|
120
123
|
} catch {
|
|
121
124
|
state.pendingFollowUp = false;
|
|
@@ -123,12 +126,11 @@ export async function continueAutoComplete(ctx: ExtensionContext): Promise<boole
|
|
|
123
126
|
}
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
export function registerAutoCompleteTurnHandlers(
|
|
127
|
-
|
|
128
|
-
pi.on("turn_start", async (_event, ctx) => {
|
|
129
|
+
export function registerAutoCompleteTurnHandlers(ext: ExtensionAPI): void {
|
|
130
|
+
ext.on("turn_start", async (_event, ctx) => {
|
|
129
131
|
resetAutoCompleteTurn(ctx);
|
|
130
132
|
});
|
|
131
|
-
|
|
133
|
+
ext.on("turn_end", async (event, ctx) => {
|
|
132
134
|
const message = event.message as { role?: string } | undefined;
|
|
133
135
|
if (message?.role === "assistant") await continueAutoComplete(ctx);
|
|
134
136
|
});
|
|
@@ -153,7 +153,7 @@ export async function initGraphCommand(args: string, ctx: CommandContext): Promi
|
|
|
153
153
|
"info",
|
|
154
154
|
);
|
|
155
155
|
try {
|
|
156
|
-
const reportPath = generateGraphReport(store, `${paths.gitCommonDir}/
|
|
156
|
+
const reportPath = generateGraphReport(store, `${paths.gitCommonDir}/pi-plans/graph`, {
|
|
157
157
|
files: report.filesScanned,
|
|
158
158
|
functions: report.functionsIndexed,
|
|
159
159
|
ms: report.durationMs,
|
|
@@ -441,7 +441,7 @@ async function runChangedPathSync(
|
|
|
441
441
|
"info",
|
|
442
442
|
);
|
|
443
443
|
try {
|
|
444
|
-
generateGraphReport(store, `${paths.gitCommonDir}/
|
|
444
|
+
generateGraphReport(store, `${paths.gitCommonDir}/pi-plans/graph`, {
|
|
445
445
|
files: report.filesScanned,
|
|
446
446
|
functions: report.functionsIndexed,
|
|
447
447
|
ms: report.durationMs,
|
|
@@ -164,7 +164,7 @@ export function computeCommunities(store: Store): {
|
|
|
164
164
|
return { communities: sortedGroups.length, nodes: order.length, godNodes };
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
/** Render GRAPH_REPORT.md (plan R-005) under `<gitCommonDir>/
|
|
167
|
+
/** Render GRAPH_REPORT.md (plan R-005) under `<gitCommonDir>/pi-plans/graph/`. */
|
|
168
168
|
export function generateGraphReport(store: Store, graphDir: string, baseline?: { files: number; functions: number; ms: number }): string {
|
|
169
169
|
const stats = computeCommunities(store);
|
|
170
170
|
const edgeStats = store.read(() =>
|
package/src/code-graph/paths.ts
CHANGED
|
@@ -34,7 +34,7 @@ export function resolveCanonicalWorktree(workdir: string): WorktreePaths {
|
|
|
34
34
|
}
|
|
35
35
|
const worktreeRoot = path.resolve(toplevel.stdout.trim());
|
|
36
36
|
const gitCommonDir = path.resolve(workdir, common.stdout.trim());
|
|
37
|
-
const stateRoot = path.join(gitCommonDir, "
|
|
37
|
+
const stateRoot = path.join(gitCommonDir, "pi-plans");
|
|
38
38
|
if (!resolveStateRootOrNull(workdir)) {
|
|
39
39
|
throw new PathError(`pi-plans state missing; run a planning init first`);
|
|
40
40
|
}
|
package/src/code-graph/watch.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* pinned to the pi extension hooks — started by /watch-graph (or session_start
|
|
5
5
|
* when the enabled marker is set), stopped by /unwatch-graph, disable-graph,
|
|
6
6
|
* and an idempotent session_shutdown handler. A PID+heartbeat lock under
|
|
7
|
-
* .git/
|
|
7
|
+
* .git/pi-plans/graph/watch keeps a single writer per worktree across pi
|
|
8
8
|
* sessions; per-file single-flight dedupes apply-trigger vs watch events.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -32,7 +32,7 @@ interface ActiveWatcher {
|
|
|
32
32
|
const activeWatchers = new Map<string, ActiveWatcher>();
|
|
33
33
|
|
|
34
34
|
function graphDir(paths: WorktreePaths): string {
|
|
35
|
-
return path.join(paths.gitCommonDir, "
|
|
35
|
+
return path.join(paths.gitCommonDir, "pi-plans", "graph");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function lockPath(paths: WorktreePaths): string {
|
package/src/compaction.ts
CHANGED
|
@@ -994,9 +994,9 @@ function phaseContextLines(context?: PiPlansVccPhaseContext): Partial<Record<typ
|
|
|
994
994
|
const outstandingContext: string[] = [];
|
|
995
995
|
if (context.phase === "execution") {
|
|
996
996
|
sessionGoal.push(context.planPath ? `Execute accepted plan ${context.planPath}` : "Execute the accepted pi-plans plan");
|
|
997
|
-
if (context.currentI) outstandingContext.push(`Current
|
|
998
|
-
if (context.remainingVerifierIds?.length) outstandingContext.push(`Remaining
|
|
999
|
-
if (context.implementationIds?.length) outstandingContext.push(`
|
|
997
|
+
if (context.currentI) outstandingContext.push(`Current task: ${context.currentI}`);
|
|
998
|
+
if (context.remainingVerifierIds?.length) outstandingContext.push(`Remaining verification checks: ${context.remainingVerifierIds.slice(0, 12).join(", ")}`);
|
|
999
|
+
if (context.implementationIds?.length) outstandingContext.push(`Plan tasks: ${context.implementationIds.slice(0, 24).join(", ")}`);
|
|
1000
1000
|
} else {
|
|
1001
1001
|
sessionGoal.push(context.runId ? `Continue active planning run ${context.runId}` : "Continue active pi-plans planning");
|
|
1002
1002
|
if (context.planPath) outstandingContext.push(`Latest plan path from session: ${context.planPath}`);
|