Commit 696f6ca3870d

Vincent Demeester <vincent@sbr.pm>
2026-02-11 15:52:51
feat(pi/github): add full content preview and improved worktree detection
PR/Issue Creation Improvements: - Add 3-option selector for long bodies (>200 chars) - Allow previewing full content before creation - Show body length and offer preview in confirmation dialog - Keep simple confirm for short bodies Worktree Detection Improvements: - Prioritize git_worktree tool results (most reliable) - Detect worktree from session tool call history - Check for { path, branch } in git_worktree details - Verify worktree still exists before using - Fallback to bash history scanning if needed This fixes PR creation when AI creates worktree but session cwd hasn't changed - GitHub extension now finds the active worktree from session history.
1 parent 9a3257d
Changed files (3)
dots
pi
agent
extensions
dots/pi/agent/extensions/github/actions/issue.ts
@@ -190,14 +190,52 @@ export async function handleIssueCreate(
 
 	// APPROVAL GATE
 	if (ctx.hasUI) {
-		const confirmMessage = buildIssueCreateConfirmation(params);
-		const confirmed = await ctx.ui.confirm("Create Issue?", confirmMessage);
-		if (!confirmed) {
-			ctx.ui.notify("Issue creation cancelled", "info");
-			return {
-				content: [{ type: "text", text: "Issue creation cancelled by user" }],
-				details: { action: "issue-create", cancelled: true } as GhDetails,
-			};
+		let confirmMessage = buildIssueCreateConfirmation(params);
+		
+		// If body is long, offer to preview full content
+		if (params.body && params.body.length > 200) {
+			confirmMessage += `\n\n๐Ÿ“„ Body: ${params.body.length} characters (truncated in preview)`;
+			
+			const choice = await ctx.ui.select(
+				"Create Issue?",
+				["Create issue", "Preview body first", "Cancel"]
+			);
+			
+			if (choice === undefined || choice === "Cancel") {
+				ctx.ui.notify("Issue creation cancelled", "info");
+				return {
+					content: [{ type: "text", text: "Issue creation cancelled by user" }],
+					details: { action: "issue-create", cancelled: true } as GhDetails,
+				};
+			}
+			
+			if (choice === "Preview body first") {
+				// Show full body in editor (read-only preview via input with initial value)
+				await ctx.ui.editor(
+					`Issue Body Preview (${params.body.length} chars):\n\nTitle: ${params.title}\n\n---\n\n`,
+					params.body
+				);
+				
+				// Ask again after preview
+				const finalConfirm = await ctx.ui.confirm("Create Issue?", confirmMessage);
+				if (!finalConfirm) {
+					ctx.ui.notify("Issue creation cancelled", "info");
+					return {
+						content: [{ type: "text", text: "Issue creation cancelled by user" }],
+						details: { action: "issue-create", cancelled: true } as GhDetails,
+					};
+				}
+			}
+		} else {
+			// Short body or no body - simple confirm
+			const confirmed = await ctx.ui.confirm("Create Issue?", confirmMessage);
+			if (!confirmed) {
+				ctx.ui.notify("Issue creation cancelled", "info");
+				return {
+					content: [{ type: "text", text: "Issue creation cancelled by user" }],
+					details: { action: "issue-create", cancelled: true } as GhDetails,
+				};
+			}
 		}
 	}
 
dots/pi/agent/extensions/github/actions/pr.ts
@@ -250,13 +250,52 @@ export async function handlePRCreate(
 		if (template) {
 			confirmMessage += `\n\nTemplate: ${template}`;
 		}
-		const confirmed = await ctx.ui.confirm("Create Pull Request?", confirmMessage);
-		if (!confirmed) {
-			ctx.ui.notify("PR creation cancelled", "info");
-			return {
-				content: [{ type: "text", text: "PR creation cancelled by user" }],
-				details: { action: "pr-create", cancelled: true } as GhDetails,
-			};
+		
+		// If body is long, offer to preview full content
+		if (params.body && params.body.length > 200) {
+			confirmMessage += `\n\n๐Ÿ“„ Body: ${params.body.length} characters (truncated in preview)`;
+			confirmMessage += `\n   Press 'y' to create, 'p' to preview full body, 'n' to cancel`;
+			
+			const choice = await ctx.ui.select(
+				"Create Pull Request?",
+				["Create PR", "Preview body first", "Cancel"]
+			);
+			
+			if (choice === undefined || choice === "Cancel") {
+				ctx.ui.notify("PR creation cancelled", "info");
+				return {
+					content: [{ type: "text", text: "PR creation cancelled by user" }],
+					details: { action: "pr-create", cancelled: true } as GhDetails,
+				};
+			}
+			
+			if (choice === "Preview body first") {
+				// Show full body in editor (read-only preview via input with initial value)
+				await ctx.ui.editor(
+					`PR Body Preview (${params.body.length} chars):\n\nTitle: ${params.title}\n\n---\n\n`,
+					params.body
+				);
+				
+				// Ask again after preview
+				const finalConfirm = await ctx.ui.confirm("Create Pull Request?", confirmMessage);
+				if (!finalConfirm) {
+					ctx.ui.notify("PR creation cancelled", "info");
+					return {
+						content: [{ type: "text", text: "PR creation cancelled by user" }],
+						details: { action: "pr-create", cancelled: true } as GhDetails,
+					};
+				}
+			}
+		} else {
+			// Short body or no body - simple confirm
+			const confirmed = await ctx.ui.confirm("Create Pull Request?", confirmMessage);
+			if (!confirmed) {
+				ctx.ui.notify("PR creation cancelled", "info");
+				return {
+					content: [{ type: "text", text: "PR creation cancelled by user" }],
+					details: { action: "pr-create", cancelled: true } as GhDetails,
+				};
+			}
 		}
 	}
 
dots/pi/agent/extensions/github/utils.ts
@@ -33,7 +33,7 @@ async function findGitRoot(pi: ExtensionAPI, cwd: string): Promise<string | null
 
 /**
  * Detect if we're working in a git worktree context.
- * Checks recent bash operations to find where actual work is happening.
+ * Checks git_worktree tool results and recent bash operations.
  * Returns the worktree path if detected, null otherwise.
  */
 async function detectWorktreeContext(
@@ -53,8 +53,45 @@ async function detectWorktreeContext(
 		}
 	}
 	
-	// Strategy 2: Check for recent git operations in session messages
-	// Look for bash tool results with git commands that mention worktree paths
+	// Strategy 2: Check for git_worktree tool results (most reliable)
+	try {
+		const entries = ctx.sessionManager.getBranch();
+		let mostRecentWorktree: string | null = null;
+		let mostRecentTimestamp = 0;
+		
+		for (const entry of entries) {
+			if (entry.type !== "message") continue;
+			const msg = entry.message;
+			
+			// Look for git_worktree tool results (from custom git extension)
+			if (msg.role === "toolResult" && msg.toolName === "git_worktree") {
+				const details = msg.details as any;
+				const timestamp = entry.timestamp || 0;
+				
+				// git_worktree create returns: { path: string, branch: string }
+				if (details?.path && timestamp > mostRecentTimestamp) {
+					mostRecentWorktree = details.path;
+					mostRecentTimestamp = timestamp;
+				}
+			}
+		}
+		
+		if (mostRecentWorktree) {
+			// Verify it still exists and is a valid git directory
+			const checkResult = await pi.exec("git", ["rev-parse", "--show-toplevel"], { 
+				cwd: mostRecentWorktree, 
+				timeout: 5000 
+			});
+			if (checkResult.code === 0 && checkResult.stdout.trim()) {
+				cachedWorktreeRoot = checkResult.stdout.trim();
+				return cachedWorktreeRoot;
+			}
+		}
+	} catch {
+		// Ignore errors in git_worktree detection
+	}
+	
+	// Strategy 3: Fallback to checking bash history for worktree operations
 	try {
 		const entries = ctx.sessionManager.getBranch();
 		for (const entry of entries.reverse()) { // Start from most recent
@@ -79,7 +116,7 @@ async function detectWorktreeContext(
 			}
 		}
 	} catch {
-		// Ignore errors in session scanning
+		// Ignore errors in bash history scanning
 	}
 	
 	return null;