Skip to content

Commit 3bd496c

Browse files
committed
feat(extensions): restructure LSP feedback pipeline and trim tool surface
Consolidate the LSP subsystem into a staged feedback pipeline with clearer diagnostic auto-fix flow, drop `codegraph_status` and `lsp_rename`, and tighten notifier, subagent, tui, web-search, and yeet extension code. Update AGENTS.md to reflect the trimmed tool list and new shared agent-status vocabulary module.
1 parent 9a9e604 commit 3bd496c

38 files changed

Lines changed: 294 additions & 422 deletions

.pi/agent/SYSTEM.md

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
- **Ambiguous** (underspecified, multiple valid readings, or assumption user wants say in): stop, use ask_user_question. NEVER guess, drift.
44
- **Non-mutation**: execute immediately, no asking.
5-
- **Mutation**: print + wait (user runs unless they say "you do it"):
6-
- Cluster/cloud writes: `kubectl apply/delete/patch`, `helm install`, `terraform apply`, `gcloud/aws create/delete/update`
7-
- Destructive: `rm -rf`, `docker push`, `npm publish`, db migrations
8-
- Installs: `pi install`, `npm install -g`, `brew install/uninstall`
5+
- **Mutation**: print + wait (user runs unless they say "you do it"). Includes cluster/cloud writes, destructive ops (incl. publish/push to registries, db migrations), installs (any package manager, incl. global/system).
96
- **Production**: NEVER mutate. Print only. If unsure target is prod, ask.
107

118
## Code
@@ -15,14 +12,10 @@
1512
- No features, abstractions, or configurability beyond what was asked.
1613
- NEVER "improve" or refactor adjacent code that isn't broken. Match existing style.
1714
- Remove imports/vars/functions YOUR changes orphaned. NEVER touch pre-existing dead code; mention it instead.
18-
- Comments: short, WHY not WHAT; none if obvious. No history ("Replaces…", "Legacy…"); write current state. No decorative dividers (`# ====`, banners) or em-dashes (—).
15+
- Comments: short, WHY not WHAT; none if obvious. No history ("Replaces…", "Legacy…"); write current state. No decorative dividers (`# ====`, banners).
1916

2017
## Tone
2118

22-
Respond terse like smart caveman. Substance stays, fluff dies. Active every response, every turn, even if unsure; NEVER drift back to filler. Off when "normal mode".
19+
Be extremely concise. Sacrifice grammar for the sake of concision.
2320

24-
Drop: articles, filler (just/really/basically), hedging, apologies, self-narration ("Let me..."), pleasantries and sycophantic openers ("Of course", "You're right", "Great question"). NEVER restate the question. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). No tool-call narration, no decorative tables/emoji, no long raw error dumps; quote shortest decisive line. Technical terms, code blocks, errors verbatim.
25-
26-
Preserve user's language; compress style, not language. NEVER name or announce the style.
27-
28-
Pattern: `[thing] [action] [reason]. [next step].`
21+
Drop: articles, filler, hedging, apologies, self-narration ("Let me..."), sycophantic openers ("You're right"). NEVER restate the question. Fragments OK. Short synonyms (fix not "implement a solution for"). No tool-call narration, no decorative tables/emoji/em-dashes (—), no raw error dumps; quote shortest decisive line. Technical terms, code blocks, errors verbatim.

.pi/agent/extensions/AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ Read referenced `.md` files completely and follow their cross-references before
3030

3131
- **`web-search.ts`**`web_search` / `fetch_content`: search the web and pull a page's readable content as markdown, for basic research without leaving the terminal. GitHub repo/file/dir links are cloned locally instead of scraped, so `read`/`bash` can explore real files.
3232
- **`eval/`** — persistent Python + JS kernels the model runs code in; cells can call pi's own tools and keep bulk data out of history. For "read N files, aggregate, summarize" work.
33-
- **`lsp/`** — the LSP subsystem. Symbol-precise nav tools: `lsp_hover` (type/docs), `lsp_definition` / `lsp_type_definition` / `lsp_implementation`, `lsp_references`, `lsp_document_symbols` (file outline), `lsp_rename` (workspace-wide), `lsp_diagnostics` (on-demand, read-only per-file error/warning check instead of a full `tsc`). Also the post-edit feedback pass (`lsp/feedback/`): formats your edits and auto-fixes their diagnostics in the background (root-cause, no suppress directives, never touching files unrelated to a diagnostic), surfacing the changes so you needn't re-read; anything left unfixed is flagged. `/lsp-fix` toggles that background auto-fix per session (`/lsp-fix on|off` to set explicitly); launch with `--lsp-fix=false` to default it off.
34-
- **`codegraph.ts`** — symbol-aware repo navigation + call-graph over the [codegraph CLI](https://github.com/colbymchenry/codegraph): `codegraph_status` / `_context` / `_search` / `_files` / `_callers` / `_callees` / `_impact` (blast-radius) / `_affected` (test selection).
33+
- **`lsp/`** — the LSP subsystem. Symbol-precise nav tools: `lsp_hover` (type/docs), `lsp_definition` / `lsp_type_definition` / `lsp_implementation`, `lsp_references`, `lsp_document_symbols` (file outline), `lsp_diagnostics` (on-demand, read-only per-file error/warning check instead of a full `tsc`). Also the post-edit feedback pass (`lsp/feedback/`): formats your edits and auto-fixes their diagnostics in the background (root-cause, no suppress directives, never touching files unrelated to a diagnostic), surfacing the changes so you needn't re-read; anything left unfixed is flagged. `/lsp-fix` toggles that background auto-fix per session (`/lsp-fix on|off` to set explicitly); launch with `--lsp-fix=false` to default it off.
34+
- **`codegraph.ts`** — symbol-aware repo navigation + call-graph over the [codegraph CLI](https://github.com/colbymchenry/codegraph): `codegraph_context` / `_search` / `_files` / `_callers` / `_callees` / `_impact` (blast-radius) / `_affected` (test selection).
3535
- **`github-pr.ts`**`github_pr` fetches a PR as signal-only markdown (metadata, description, changed files, failing checks, unresolved review threads — including bot inline findings like CodeRabbit). Drops commit/timeline noise, resolved threads, and bot release-note/walkthrough issue comments; diff is opt-in via `diff:true`. Use instead of `gh pr view`.
3636
- **`subagent.ts`**`/subagent` delegates a task to an isolated `pi` agent (single-layer, no recursion), running visibly in its own tmux pane instead of a hidden background process — watch, scroll, or step in directly. Agents defined in `~/.pi/agent/agents/*.md` — each file's body is the subagent's entire system prompt, no shared preamble or other context added, for an unambiguous clean start; `hidden: true` agents stay invocable by name (e.g. from a skill) without appearing in the tool's agent list. For offloading research/recon/implementation off the main thread. tmux-only: the tool isn't available outside a tmux session.
3737
- **`ask/`**`ask_user_question`: presents a tabbed multiple-choice questionnaire (single/multi-select, free-text + "chat" fallbacks, review tab) instead of guessing when a request is ambiguous. Length limits on labels are soft so the first call always lands.
@@ -69,5 +69,5 @@ extensions/
6969
├── ask/ ask_user_question questionnaire dialog
7070
├── eval/ persistent kernels + bridge
7171
├── lsp/ headless nvim, nav tools + post-edit feedback
72-
└── shared/ side-channel LLM helper, message extraction, widget factory
72+
└── shared/ side-channel LLM helper, message extraction, widget factory, agent-status vocabulary
7373
```

.pi/agent/extensions/ask/index.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,15 @@ function clampParams(params: QuestionParams): QuestionParams {
5555

5656
const DESCRIPTION = `Present the user with one or more structured multiple-choice questions and wait for their answer. This is the PRIMARY way to resolve ambiguity — reach for it directly instead of guessing or asking in plain prose, and call it in the SAME turn you hit the ambiguity (don't first write out the question as text).
5757
58-
Call it when:
59-
1. The request is underspecified and you cannot proceed without a concrete decision.
60-
2. Multiple reasonable interpretations or implementation paths exist.
61-
3. You're about to make an assumption the user would want a say in.
62-
4. You want to offer a clear set of directions to take.
58+
Call it when the request is ambiguous (underspecified, multiple valid readings, or an assumption the user would want a say in), or to offer a clear set of directions to take.
6359
6460
Mechanics:
6561
- Up to ${MAX_QUESTIONS} questions per call, each with ${MIN_OPTIONS}-${MAX_OPTIONS} options. Group all clarifying questions into ONE call — never stack back-to-back calls.
66-
- Each option needs a concise label and a description of what it means / its trade-off.
67-
- header / label length limits are soft: over-long values are auto-truncated, NEVER rejected — don't avoid the tool or pad/trim to fit.
68-
- The user can always type a custom answer ("Type something." is appended automatically to single-select questions) or pick "Chat about this" to abandon the questionnaire and talk it through.
69-
- Set multiSelect: true when multiple answers are valid (suppresses the "Type something." row).
70-
- If you recommend an option, make it the FIRST option and append "(Recommended)" to its label.
71-
- Do NOT author options labeled "Other", "Type something.", "Chat about this", or "Next" — these are reserved and rejected.`;
62+
- header / label length limits are soft: over-long values auto-truncate, NEVER rejected — don't avoid the tool or pad/trim to fit.
63+
- Single-select questions get a free-text "Type something." row automatically; the user can also pick "Chat about this" to abandon and talk it through.
64+
- Set multiSelect: true when multiple answers are valid (suppresses the free-text row).
65+
- If you recommend an option, make it FIRST and append "(Recommended)" to its label.
66+
- Do NOT author options labeled "Other", "Type something.", "Chat about this", or "Next" — reserved, rejected.`;
7267

7368
export function registerAskUserQuestionTool(pi: ExtensionAPI): void {
7469
pi.registerTool({

.pi/agent/extensions/ask/schema.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ export const QuestionParamsSchema = Type.Object({
155155
}),
156156
});
157157

158-
export type OptionData = Static<typeof OptionSchema>;
159158
export type QuestionData = Static<typeof QuestionSchema>;
160159
export type QuestionParams = Static<typeof QuestionParamsSchema>;
161160

.pi/agent/extensions/codegraph.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,6 @@ type Node = {
5656
const loc = (n: Node) => `${n.filePath}:${n.startLine}`;
5757
const nodeRow = (n: Node) => `${n.name} ${n.kind} ${loc(n)}`;
5858

59-
function fmtStatus(s: Record<string, unknown>): string {
60-
const kinds = Object.entries((s.nodesByKind as Record<string, number>) ?? {})
61-
.map(([k, v]) => `${k}=${v}`)
62-
.join(" ");
63-
const p = (s.pendingChanges as Record<string, number>) ?? {};
64-
return [
65-
`files=${s.fileCount} nodes=${s.nodeCount} edges=${s.edgeCount}`,
66-
`languages=${((s.languages as string[]) ?? []).join(",")}`,
67-
kinds && `kinds: ${kinds}`,
68-
`pending: +${p.added ?? 0} ~${p.modified ?? 0} -${p.removed ?? 0}`,
69-
s.lastIndexed && `lastIndexed=${s.lastIndexed}`,
70-
]
71-
.filter(Boolean)
72-
.join("\n");
73-
}
74-
7559
function fmtQuery(
7660
arr: Array<{ node: Node & { isExported?: boolean } }>,
7761
): string {
@@ -182,19 +166,6 @@ export default function (pi: ExtensionAPI) {
182166
child.unref();
183167
});
184168

185-
pi.registerTool({
186-
name: "codegraph_status",
187-
label: "CodeGraph status",
188-
description:
189-
"Inspect codegraph index: file count, node count, languages, pending changes.",
190-
parameters: Type.Object({}),
191-
async execute(_id, _raw, signal, _onUpdate, ctx) {
192-
return callCodegraph(["status", "-j"], ctx.cwd, signal, (j) =>
193-
fmtStatus(j as Record<string, unknown>),
194-
);
195-
},
196-
});
197-
198169
pi.registerTool({
199170
name: "codegraph_context",
200171
label: "CodeGraph context",

.pi/agent/extensions/eval/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ export default function (pi: ExtensionAPI) {
345345
label: "Eval",
346346
description:
347347
"Run code in persistent Python and JavaScript kernels for iterative, stateful work — state persists across cells and across separate tool calls, in one kernel per language per session. Not for a single one-off command; use `bash`/`read` directly for those.\n\n" +
348-
'Cell shape: `{language: "py"|"js", code, title?, timeout?, reset?}`.\n\n' +
349348
'Inside cell code, `tool.<name>({...})` invokes pi built-in tools (`read`, `write`, `edit`, `bash`, `grep`, `find`, `ls` take their normal pi argument schemas; `tree({path,max_depth})` is extra). Shortcuts: `read(path)`, `write(path,content)`, `tree(path)`, `env(key?, value?)` (no args: full env dict; one: get; two: set), `completion(prompt, model?, system?, schema?)` for a oneshot stateless model call (model: "default" or "provider/id"; schema: JSON-Schema for structured output).\n\n' +
350349
'Call `install("pkg1", "pkg2")` to add Python packages, persists across pi restarts. JS cells support top-level await and package imports; use `globalThis` / `state` to persist values across cells.',
351350
promptSnippet:

.pi/agent/extensions/eval/types.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@
22

33
export type Language = "py" | "js";
44

5-
export interface Cell {
6-
language: Language;
7-
code: string;
8-
title?: string;
9-
timeout?: number;
10-
reset?: boolean;
11-
}
12-
135
export interface DisplayItem {
146
mime: string;
157
data: string;
@@ -51,9 +43,7 @@ export interface KernelEventDone {
5143
error: string | null;
5244
}
5345
export type KernelEvent =
54-
| KernelEventDisplay
55-
| KernelEventStream
56-
| KernelEventDone;
46+
KernelEventDisplay | KernelEventStream | KernelEventDone;
5747

5848
// Bridge protocol — Python prelude -> host over loopback HTTP.
5949
export interface BridgeRequest {

.pi/agent/extensions/folder-context.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export default function (pi: ExtensionAPI) {
2828
injected.clear();
2929
});
3030

31-
pi.on("tool_call", async (event, ctx) => {
31+
// Hook tool_result, not tool_call: a steer message sent during tool_call
32+
// lands between the assistant tool_use and its tool_result, which Anthropic
33+
// rejects (400: tool_result must follow its tool_use).
34+
pi.on("tool_result", async (event, ctx) => {
3235
if (!TARGET_TOOLS.has(event.toolName)) return;
3336
const rawPath = (event.input as { path?: unknown }).path;
3437
if (typeof rawPath !== "string" || rawPath === "") return;

.pi/agent/extensions/github-pr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export default function (pi: ExtensionAPI) {
131131
return new Text(theme.fg("dim", ` ${s}`), 0, 0);
132132
},
133133
description:
134-
"Fetch a GitHub PR (URL or number) as signal-only markdown: metadata, description, changed files, failing checks, and unresolved review threads. Use instead of `gh pr view`. diff:true to include the unified diff.",
134+
"Fetch a GitHub PR (URL or number) as signal-only markdown: metadata, description, changed files, failing checks, and unresolved review threads. Use instead of `gh pr view`.",
135135
parameters: params,
136136
async execute(_id, raw, signal, _onUpdate, _ctx) {
137137
const a = raw as {

.pi/agent/extensions/go.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ async function go(ctx: ExtensionContext): Promise<void> {
104104
}
105105
}
106106

107-
// --- Stream rules (TTSR-lite) -----------------------------------------
107+
// Stream rules (TTSR-lite):
108108
//
109109
// A regex hits the model's streaming text/thinking output → the turn aborts
110110
// → a hidden reminder is appended as a user turn → generation resumes via

0 commit comments

Comments
 (0)