From 4e11daa5210a96224648cea56cc6d99f1b6c1d69 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sat, 2 May 2026 15:49:07 +0530 Subject: [PATCH] chore: remove checked-in pi workspace files --- .pi/extensions/diff.ts | 117 ----------------- .pi/extensions/files.ts | 134 -------------------- .pi/extensions/prompt-url-widget.ts | 190 ---------------------------- .pi/extensions/redraws.ts | 26 ---- .pi/extensions/ui/paged-select.ts | 82 ------------ .pi/git/.gitignore | 2 - .pi/prompts/cl.md | 58 --------- .pi/prompts/is.md | 22 ---- scripts/check-duplicates.mjs | 2 +- 9 files changed, 1 insertion(+), 632 deletions(-) delete mode 100644 .pi/extensions/diff.ts delete mode 100644 .pi/extensions/files.ts delete mode 100644 .pi/extensions/prompt-url-widget.ts delete mode 100644 .pi/extensions/redraws.ts delete mode 100644 .pi/extensions/ui/paged-select.ts delete mode 100644 .pi/git/.gitignore delete mode 100644 .pi/prompts/cl.md delete mode 100644 .pi/prompts/is.md diff --git a/.pi/extensions/diff.ts b/.pi/extensions/diff.ts deleted file mode 100644 index 9f8e718e892..00000000000 --- a/.pi/extensions/diff.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Diff Extension - * - * /diff command shows modified/deleted/new files from git status and opens - * the selected file in VS Code's diff view. - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { showPagedSelectList } from "./ui/paged-select"; - -interface FileInfo { - status: string; - statusLabel: string; - file: string; -} - -export default function (pi: ExtensionAPI) { - pi.registerCommand("diff", { - description: "Show git changes and open in VS Code diff view", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("No UI available", "error"); - return; - } - - // Get changed files from git status - const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd }); - - if (result.code !== 0) { - ctx.ui.notify(`git status failed: ${result.stderr}`, "error"); - return; - } - - if (!result.stdout || !result.stdout.trim()) { - ctx.ui.notify("No changes in working tree", "info"); - return; - } - - // Parse git status output - // Format: XY filename (where XY is two-letter status, then space, then filename) - const lines = result.stdout.split("\n"); - const files: FileInfo[] = []; - - for (const line of lines) { - if (line.length < 4) { - continue; - } // Need at least "XY f" - - const status = line.slice(0, 2); - const file = line.slice(2).trimStart(); - - // Translate status codes to short labels - let statusLabel: string; - if (status.includes("M")) { - statusLabel = "M"; - } else if (status.includes("A")) { - statusLabel = "A"; - } else if (status.includes("D")) { - statusLabel = "D"; - } else if (status.includes("?")) { - statusLabel = "?"; - } else if (status.includes("R")) { - statusLabel = "R"; - } else if (status.includes("C")) { - statusLabel = "C"; - } else { - statusLabel = status.trim() || "~"; - } - - files.push({ status: statusLabel, statusLabel, file }); - } - - if (files.length === 0) { - ctx.ui.notify("No changes found", "info"); - return; - } - - const openSelected = async (fileInfo: FileInfo): Promise => { - try { - // Open in VS Code diff view. - // For untracked files, git difftool won't work, so fall back to just opening the file. - if (fileInfo.status === "?") { - await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd }); - return; - } - - const diffResult = await pi.exec( - "git", - ["difftool", "-y", "--tool=vscode", fileInfo.file], - { - cwd: ctx.cwd, - }, - ); - if (diffResult.code !== 0) { - await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd }); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error"); - } - }; - - const items = files.map((file) => ({ - value: file, - label: `${file.status} ${file.file}`, - })); - await showPagedSelectList({ - ctx, - title: " Select file to diff", - items, - onSelect: (item) => { - void openSelected(item.value as FileInfo); - }, - }); - }, - }); -} diff --git a/.pi/extensions/files.ts b/.pi/extensions/files.ts deleted file mode 100644 index e1325303521..00000000000 --- a/.pi/extensions/files.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Files Extension - * - * /files command lists all files the model has read/written/edited in the active session branch, - * coalesced by path and sorted newest first. Selecting a file opens it in VS Code. - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { showPagedSelectList } from "./ui/paged-select"; - -interface FileEntry { - path: string; - operations: Set<"read" | "write" | "edit">; - lastTimestamp: number; -} - -type FileToolName = "read" | "write" | "edit"; - -export default function (pi: ExtensionAPI) { - pi.registerCommand("files", { - description: "Show files read/written/edited in this session", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("No UI available", "error"); - return; - } - - // Get the current branch (path from leaf to root) - const branch = ctx.sessionManager.getBranch(); - - // First pass: collect tool calls (id -> {path, name}) from assistant messages - const toolCalls = new Map(); - - for (const entry of branch) { - if (entry.type !== "message") { - continue; - } - const msg = entry.message; - - if (msg.role === "assistant" && Array.isArray(msg.content)) { - for (const block of msg.content) { - if (block.type === "toolCall") { - const name = block.name; - if (name === "read" || name === "write" || name === "edit") { - const path = block.arguments?.path; - if (path && typeof path === "string") { - toolCalls.set(block.id, { path, name, timestamp: msg.timestamp }); - } - } - } - } - } - } - - // Second pass: match tool results to get the actual execution timestamp - const fileMap = new Map(); - - for (const entry of branch) { - if (entry.type !== "message") { - continue; - } - const msg = entry.message; - - if (msg.role === "toolResult") { - const toolCall = toolCalls.get(msg.toolCallId); - if (!toolCall) { - continue; - } - - const { path, name } = toolCall; - const timestamp = msg.timestamp; - - const existing = fileMap.get(path); - if (existing) { - existing.operations.add(name); - if (timestamp > existing.lastTimestamp) { - existing.lastTimestamp = timestamp; - } - } else { - fileMap.set(path, { - path, - operations: new Set([name]), - lastTimestamp: timestamp, - }); - } - } - } - - if (fileMap.size === 0) { - ctx.ui.notify("No files read/written/edited in this session", "info"); - return; - } - - // Sort by most recent first - const files = Array.from(fileMap.values()).toSorted( - (a, b) => b.lastTimestamp - a.lastTimestamp, - ); - - const openSelected = async (file: FileEntry): Promise => { - try { - await pi.exec("code", ["-g", file.path], { cwd: ctx.cwd }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error"); - } - }; - - const items = files.map((file) => { - const ops: string[] = []; - if (file.operations.has("read")) { - ops.push("R"); - } - if (file.operations.has("write")) { - ops.push("W"); - } - if (file.operations.has("edit")) { - ops.push("E"); - } - return { - value: file, - label: `${ops.join("")} ${file.path}`, - }; - }); - await showPagedSelectList({ - ctx, - title: " Select file to open", - items, - onSelect: (item) => { - void openSelected(item.value as FileEntry); - }, - }); - }, - }); -} diff --git a/.pi/extensions/prompt-url-widget.ts b/.pi/extensions/prompt-url-widget.ts deleted file mode 100644 index e39c7fd949b..00000000000 --- a/.pi/extensions/prompt-url-widget.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { - DynamicBorder, - type ExtensionAPI, - type ExtensionContext, -} from "@mariozechner/pi-coding-agent"; -import { Container, Text } from "@mariozechner/pi-tui"; - -const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:\s*(\S+)/im; -const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im; - -type PromptMatch = { - kind: "pr" | "issue"; - url: string; -}; - -type GhMetadata = { - title?: string; - author?: { - login?: string; - name?: string | null; - }; -}; - -function extractPromptMatch(prompt: string): PromptMatch | undefined { - const prMatch = prompt.match(PR_PROMPT_PATTERN); - if (prMatch?.[1]) { - return { kind: "pr", url: prMatch[1].trim() }; - } - - const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN); - if (issueMatch?.[1]) { - return { kind: "issue", url: issueMatch[1].trim() }; - } - - return undefined; -} - -async function fetchGhMetadata( - pi: ExtensionAPI, - kind: PromptMatch["kind"], - url: string, -): Promise { - const args = - kind === "pr" - ? ["pr", "view", url, "--json", "title,author"] - : ["issue", "view", url, "--json", "title,author"]; - - try { - const result = await pi.exec("gh", args); - if (result.code !== 0 || !result.stdout) { - return undefined; - } - return JSON.parse(result.stdout) as GhMetadata; - } catch { - return undefined; - } -} - -function formatAuthor(author?: GhMetadata["author"]): string | undefined { - if (!author) { - return undefined; - } - const name = author.name?.trim(); - const login = author.login?.trim(); - if (name && login) { - return `${name} (@${login})`; - } - if (login) { - return `@${login}`; - } - if (name) { - return name; - } - return undefined; -} - -export default function promptUrlWidgetExtension(pi: ExtensionAPI) { - const setWidget = ( - ctx: ExtensionContext, - match: PromptMatch, - title?: string, - authorText?: string, - ) => { - ctx.ui.setWidget("prompt-url", (_tui, thm) => { - const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url); - const authorLine = authorText ? thm.fg("muted", authorText) : undefined; - const urlLine = thm.fg("dim", match.url); - - const lines = [titleText]; - if (authorLine) { - lines.push(authorLine); - } - lines.push(urlLine); - - const container = new Container(); - container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s))); - container.addChild(new Text(lines.join("\n"), 1, 0)); - return container; - }); - }; - - const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => { - const label = match.kind === "pr" ? "PR" : "Issue"; - const trimmedTitle = title?.trim(); - const fallbackName = `${label}: ${match.url}`; - const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName; - const currentName = pi.getSessionName()?.trim(); - if (!currentName) { - pi.setSessionName(desiredName); - return; - } - if (currentName === match.url || currentName === fallbackName) { - pi.setSessionName(desiredName); - } - }; - - const renderPromptMatch = (ctx: ExtensionContext, match: PromptMatch) => { - setWidget(ctx, match); - applySessionName(ctx, match); - void fetchGhMetadata(pi, match.kind, match.url).then((meta) => { - const title = meta?.title?.trim(); - const authorText = formatAuthor(meta?.author); - setWidget(ctx, match, title, authorText); - applySessionName(ctx, match, title); - }); - }; - - pi.on("before_agent_start", async (event, ctx) => { - if (!ctx.hasUI) { - return; - } - const match = extractPromptMatch(event.prompt); - if (!match) { - return; - } - - renderPromptMatch(ctx, match); - }); - - pi.on("session_switch", async (_event, ctx) => { - rebuildFromSession(ctx); - }); - - const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => { - if (!content) { - return ""; - } - if (typeof content === "string") { - return content; - } - return ( - content - .filter((block): block is { type: "text"; text: string } => block.type === "text") - .map((block) => block.text) - .join("\n") ?? "" - ); - }; - - const rebuildFromSession = (ctx: ExtensionContext) => { - if (!ctx.hasUI) { - return; - } - - const entries = ctx.sessionManager.getEntries(); - const lastMatch = [...entries].toReversed().find((entry) => { - if (entry.type !== "message" || entry.message.role !== "user") { - return false; - } - const text = getUserText(entry.message.content); - return !!extractPromptMatch(text); - }); - - const content = - lastMatch?.type === "message" && lastMatch.message.role === "user" - ? lastMatch.message.content - : undefined; - const text = getUserText(content); - const match = text ? extractPromptMatch(text) : undefined; - if (!match) { - ctx.ui.setWidget("prompt-url", undefined); - return; - } - - renderPromptMatch(ctx, match); - }; - - pi.on("session_start", async (_event, ctx) => { - rebuildFromSession(ctx); - }); -} diff --git a/.pi/extensions/redraws.ts b/.pi/extensions/redraws.ts deleted file mode 100644 index 6331f5eaba6..00000000000 --- a/.pi/extensions/redraws.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Redraws Extension - * - * Exposes /tui to show TUI redraw stats. - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Text } from "@mariozechner/pi-tui"; - -export default function (pi: ExtensionAPI) { - pi.registerCommand("tui", { - description: "Show TUI stats", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - return; - } - let redraws = 0; - await ctx.ui.custom((tui, _theme, _keybindings, done) => { - redraws = tui.fullRedraws; - done(undefined); - return new Text("", 0, 0); - }); - ctx.ui.notify(`TUI full redraws: ${redraws}`, "info"); - }, - }); -} diff --git a/.pi/extensions/ui/paged-select.ts b/.pi/extensions/ui/paged-select.ts deleted file mode 100644 index a92db66bc68..00000000000 --- a/.pi/extensions/ui/paged-select.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { - Container, - Key, - matchesKey, - type SelectItem, - SelectList, - Text, -} from "@mariozechner/pi-tui"; - -type CustomUiContext = { - ui: { - custom: ( - render: ( - tui: { requestRender: () => void }, - theme: { - fg: (tone: string, text: string) => string; - bold: (text: string) => string; - }, - kb: unknown, - done: () => void, - ) => { - render: (width: number) => string; - invalidate: () => void; - handleInput: (data: string) => void; - }, - ) => Promise; - }; -}; - -export async function showPagedSelectList(params: { - ctx: CustomUiContext; - title: string; - items: SelectItem[]; - onSelect: (item: SelectItem) => void; -}): Promise { - await params.ctx.ui.custom((tui, theme, _kb, done) => { - const container = new Container(); - - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - container.addChild(new Text(theme.fg("accent", theme.bold(params.title)), 0, 0)); - - const visibleRows = Math.min(params.items.length, 15); - let currentIndex = 0; - - const selectList = new SelectList(params.items, visibleRows, { - selectedPrefix: (text) => theme.fg("accent", text), - selectedText: (text) => text, - description: (text) => theme.fg("muted", text), - scrollInfo: (text) => theme.fg("dim", text), - noMatch: (text) => theme.fg("warning", text), - }); - selectList.onSelect = (item) => params.onSelect(item); - selectList.onCancel = () => done(); - selectList.onSelectionChange = (item) => { - currentIndex = params.items.indexOf(item); - }; - container.addChild(selectList); - - container.addChild( - new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0), - ); - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - - return { - render: (width) => container.render(width), - invalidate: () => container.invalidate(), - handleInput: (data) => { - if (matchesKey(data, Key.left)) { - currentIndex = Math.max(0, currentIndex - visibleRows); - selectList.setSelectedIndex(currentIndex); - } else if (matchesKey(data, Key.right)) { - currentIndex = Math.min(params.items.length - 1, currentIndex + visibleRows); - selectList.setSelectedIndex(currentIndex); - } else { - selectList.handleInput(data); - } - tui.requestRender(); - }, - }; - }); -} diff --git a/.pi/git/.gitignore b/.pi/git/.gitignore deleted file mode 100644 index d6b7ef32c84..00000000000 --- a/.pi/git/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/.pi/prompts/cl.md b/.pi/prompts/cl.md deleted file mode 100644 index 6d79ecda66e..00000000000 --- a/.pi/prompts/cl.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -description: Audit changelog entries before release ---- - -Audit changelog entries for all commits since the last release. - -## Process - -1. **Find the last release tag:** - - ```bash - git tag --sort=-version:refname | head -1 - ``` - -2. **List all commits since that tag:** - - ```bash - git log ..HEAD --oneline - ``` - -3. **Read each package's [Unreleased] section:** - - packages/ai/CHANGELOG.md - - packages/tui/CHANGELOG.md - - packages/coding-agent/CHANGELOG.md - -4. **For each commit, check:** - - Skip: changelog updates, doc-only changes, release housekeeping - - Determine which package(s) the commit affects (use `git show --stat`) - - Verify a changelog entry exists in the affected package(s) - - For external contributions (PRs), verify format: `Description ([#N](url) by [@user](url))` - -5. **Cross-package duplication rule:** - Changes in `ai`, `agent` or `tui` that affect end users should be duplicated to `coding-agent` changelog, since coding-agent is the user-facing package that depends on them. - -6. **Add New Features section after changelog fixes:** - - Insert a `### New Features` section at the start of `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`. - - Propose the top new features to the user for confirmation before writing them. - - Link to relevant docs and sections whenever possible. - -7. **Report:** - - List commits with missing entries - - List entries that need cross-package duplication - - Add any missing entries directly - -## Changelog Format Reference - -Sections (in order): - -- `### Breaking Changes` - API changes requiring migration -- `### Added` - New features -- `### Changed` - Changes to existing functionality -- `### Fixed` - Bug fixes -- `### Removed` - Removed features - -Attribution: - -- Internal: `Fixed foo ([#123](https://github.com/badlogic/pi-mono/issues/123))` -- External: `Added bar ([#456](https://github.com/badlogic/pi-mono/pull/456) by [@user](https://github.com/user))` diff --git a/.pi/prompts/is.md b/.pi/prompts/is.md deleted file mode 100644 index cc8f603adc0..00000000000 --- a/.pi/prompts/is.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -description: Analyze GitHub issues (bugs or feature requests) ---- - -Analyze GitHub issue(s): $ARGUMENTS - -For each issue: - -1. Read the issue in full, including all comments and linked issues/PRs. - -2. **For bugs**: - - Ignore any root cause analysis in the issue (likely wrong) - - Read all related code files in full (no truncation) - - Trace the code path and identify the actual root cause - - Propose a fix - -3. **For feature requests**: - - Read all related code files in full (no truncation) - - Propose the most concise implementation approach - - List affected files and changes needed - -Do NOT implement unless explicitly asked. Analyze and propose only. diff --git a/scripts/check-duplicates.mjs b/scripts/check-duplicates.mjs index e7cfea3a926..cf0c3fc1835 100644 --- a/scripts/check-duplicates.mjs +++ b/scripts/check-duplicates.mjs @@ -27,7 +27,7 @@ const sourceExtensions = new Set([".ts", ".tsx", ".js", ".mjs", ".cjs"]); const sourcePattern = "**/*.{ts,tsx,js,mjs,cjs}"; const testPattern = "**/*.{test,e2e.test,live.test}.{ts,tsx,js,mjs,cjs}"; // Keep local agent support trees and vendored snapshots classified but outside jscpd. -const intentionallyUnscannedPrefixes = [".agents/", ".pi/", "vendor/"]; +const intentionallyUnscannedPrefixes = [".agents/", "vendor/"]; const generatedIgnores = [ "extensions/qa-matrix/src/shared/**",