Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions apps/ade-cli/src/adeRpcServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2459,21 +2459,58 @@ describe("adeRpcServer", () => {
closeLinearIssueOnMerge: true,
});

const drafted = await callTool(handler, "create_pr_from_lane", {
const defaulted = await callTool(handler, "create_pr_from_lane", {
laneId: "lane-1",
baseBranch: "main",
});
expect(drafted?.isError).toBeUndefined();
expect(fixture.runtime.prService.draftDescription).toHaveBeenCalledWith({
expect(defaulted?.isError).toBeUndefined();
expect(fixture.runtime.prService.draftDescription).not.toHaveBeenCalled();
expect(fixture.runtime.prService.createFromLane).toHaveBeenLastCalledWith({
laneId: "lane-1",
baseBranch: "main",
title: "Lane 1 -> main",
body: "",
draft: false,
closeLinearIssueOnMerge: true,
});

(fixture.runtime.laneService.list as any).mockResolvedValueOnce([
{
id: "primary",
name: "Primary",
laneType: "primary",
parentLaneId: null,
baseRef: "main",
branchRef: "main",
archivedAt: null,
},
{
id: "parent-lane",
name: "Parent",
laneType: "worktree",
parentLaneId: null,
baseRef: "main",
branchRef: "feature/parent",
archivedAt: null,
},
{
id: "child-lane",
name: "Child",
laneType: "worktree",
parentLaneId: "parent-lane",
baseRef: "main",
branchRef: "feature/child",
archivedAt: null,
},
]);
const stackedDefaulted = await callTool(handler, "create_pr_from_lane", {
laneId: "child-lane",
});
expect(stackedDefaulted?.isError).toBeUndefined();
expect(fixture.runtime.prService.createFromLane).toHaveBeenLastCalledWith({
laneId: "lane-1",
baseBranch: "main",
title: "Drafted PR",
body: "Drafted body",
laneId: "child-lane",
title: "Child -> Parent",
body: "",
draft: false,
closeLinearIssueOnMerge: true,
});
Expand Down
54 changes: 44 additions & 10 deletions apps/ade-cli/src/adeRpcServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { resolvePathWithinRoot } from "../../desktop/src/main/services/shared/ut
import { getDefaultModelDescriptor } from "../../desktop/src/shared/modelRegistry";
import { buildAdeCliInlineGuidance } from "../../desktop/src/shared/adeCliGuidance";
import { buildDeeplink, isValidCommitSha, isValidRepoRelativePath } from "../../desktop/src/shared/deeplinks";
import { resolveStableLaneBaseBranch } from "../../desktop/src/shared/laneBaseResolution";
import {
ADE_AGENT_SKILLS_DIRS_ENV,
getAdeAgentSkillRootsForPrompt,
Expand Down Expand Up @@ -979,7 +980,7 @@ const TOOL_SPECS: ToolSpec[] = [
},
{
name: "create_pr_from_lane",
description: "Create a PR from a lane branch. Drafts a title/body from ADE context when omitted. Returns GitHub and ADE PR URLs when available.",
description: "Create a PR from a lane branch. When omitted, the title defaults to \"source lane -> target lane\" and the body is empty. Returns GitHub and ADE PR URLs when available.",
inputSchema: {
type: "object",
required: ["laneId"],
Expand Down Expand Up @@ -2019,6 +2020,46 @@ function resolveLaneWorktreePath(runtime: AdeRuntime, laneId: string | null | un
return null;
}

function branchNameForPrTitle(ref: string | null | undefined): string {
let value = (ref ?? "").trim();
value = value.replace(/^refs\/heads\//, "");
value = value.replace(/^refs\/remotes\//, "");
value = value.replace(/^origin\//, "");
return value;
}

async function defaultPrTitleForLane(runtime: AdeRuntime, laneId: string, baseBranch?: string | null): Promise<string> {
const lanes = await runtime.laneService.list({ includeArchived: false, includeStatus: false }).catch(() => []);
const sourceLane = lanes.find((lane) => lane.id === laneId) ?? null;
const laneInfo = (() => {
try {
return typeof runtime.laneService.getLaneBaseAndBranch === "function"
? runtime.laneService.getLaneBaseAndBranch(laneId)
: null;
} catch {
return null;
}
})();
const sourceName = asOptionalTrimmedString(sourceLane?.name) || laneId;
const parentLane = sourceLane?.parentLaneId
? lanes.find((lane) => lane.id === sourceLane.parentLaneId) ?? null
: null;
const primaryLane = lanes.find((lane) => lane.laneType === "primary") ?? null;
const stableBaseBranch = sourceLane
? resolveStableLaneBaseBranch({
lane: sourceLane,
parent: parentLane,
primaryBranchRef: primaryLane?.branchRef ?? runtime.project?.baseRef ?? "main",
})
: laneInfo?.baseRef || runtime.project?.baseRef || "main";
const targetBranch = branchNameForPrTitle(baseBranch || stableBaseBranch || laneInfo?.baseRef || runtime.project?.baseRef || "main");
const targetLane = targetBranch
? lanes.find((lane) => lane.id !== laneId && branchNameForPrTitle(lane.branchRef) === targetBranch)
: null;
const targetName = asOptionalTrimmedString(targetLane?.name) || targetBranch || "target";
return `${sourceName} -> ${targetName}`;
}

function buildAdeInlineGuidanceForLane(laneWorktreePath: string | null | undefined): string {
return buildAdeCliInlineGuidance(getAdeAgentSkillRootsForPrompt({ cwd: laneWorktreePath ?? undefined }));
}
Expand Down Expand Up @@ -4469,15 +4510,8 @@ async function runTool(args: {
let title = asOptionalTrimmedString(toolArgs.title);
let body = typeof toolArgs.body === "string" ? toolArgs.body : null;
const closeLinearIssueOnMerge = asBoolean(toolArgs.closeLinearIssueOnMerge, true);
if (!title || body == null) {
const draft = await prSvc.draftDescription({
laneId,
...(baseBranch ? { baseBranch } : {}),
...(closeLinearIssueOnMerge ? { closeLinearIssueOnMerge } : {}),
});
title = title || asOptionalTrimmedString(draft.title) || `PR for ${laneId}`;
body = body ?? asOptionalTrimmedString(draft.body) ?? "";
}
if (!title) title = await defaultPrTitleForLane(runtime, laneId, baseBranch);
if (body == null) body = "";
const draft = asBoolean(toolArgs.draft, false);
const pr = await prSvc.createFromLane({
laneId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3467,7 +3467,7 @@ function registerWorkRemoteCommands({ args, register }: RemoteCommandRegistratio
return await args.ptyService.create({
laneId: parsed.laneId,
title: parsed.title,
...(parsed.toolType === "shell" || !parsed.startupCommand ? {} : { startupCommand: parsed.startupCommand }),
...(parsed.startupCommand ? { startupCommand: parsed.startupCommand } : {}),
tracked: parsed.tracked ?? true,
cols: parsed.cols ?? 120,
rows: parsed.rows ?? 36,
Expand Down
11 changes: 11 additions & 0 deletions apps/ade-cli/src/tuiClient/__tests__/ChatView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
renderChatVisibleSelectionRows,
renderChatVisibleSelectionRowsFromRows,
selectedTextFromChatRows,
workFileDiffKey,
workGroupExpandKey,
} from "../components/ChatView";
import { aggregateChatBlocks } from "../aggregate";
Expand Down Expand Up @@ -1240,6 +1241,16 @@ describe("ChatView", () => {
const expanded = renderEvents(events, { width: 120, expanded: true });
expect(expanded).toContain("early.ts");
expect(expanded).toContain("recent.ts");
expect(expanded).toContain("diff");

const rows = renderChatVisibleSelectionRows({
events,
notices: [],
activeSession: session,
width: 120,
expandedLineIds: new Set([workGroupExpandKey(chatEventLineId(events[0]!, 0))]),
});
expect(rows.some((row) => row.actionId === workFileDiffKey(chatEventLineId(events[0]!, 0), "f1"))).toBe(true);
});

it("tags a collapsed work-group header with an expandable click-target id", () => {
Expand Down
1 change: 1 addition & 0 deletions apps/ade-cli/src/tuiClient/__tests__/aggregate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe("aggregateChatBlocks typed groups", () => {
kind: "modify",
additions: 1,
deletions: 1,
diff: "+added line\n-removed line",
status: "ok",
});
expect(fileGroup!.entries[1]).toMatchObject({
Expand Down
44 changes: 44 additions & 0 deletions apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import {
promptTextForTerminal,
clipboardImageCacheRootForRuntime,
uploadClipboardImageAttachmentToRuntime,
defaultPrTitleForLane,
} from "../app";
import { isTerminalSessionResumable } from "../closedCliSessions";
import {
Expand Down Expand Up @@ -633,6 +634,49 @@ describe("lane worktree availability", () => {
});
});

describe("PR title defaults", () => {
function laneForPrTitle(overrides: Partial<LaneSummary> = {}): LaneSummary {
return {
id: "lane-1",
name: "Feature",
laneType: "worktree",
baseRef: "main",
branchRef: "feature",
worktreePath: "/tmp/feature",
parentLaneId: null,
childCount: 0,
stackDepth: 0,
parentStatus: null,
isEditProtected: false,
status: { dirty: false, ahead: 0, behind: 0, remoteBehind: 0, rebaseInProgress: false },
color: null,
icon: null,
tags: [],
createdAt: "2026-05-20T00:00:00.000Z",
...overrides,
};
}

it("uses the parent lane branch when defaulting stacked lane PR titles", () => {
const parent = laneForPrTitle({
id: "lane-parent",
name: "Parent",
branchRef: "feature/parent",
worktreePath: "/tmp/parent",
});
const child = laneForPrTitle({
id: "lane-child",
name: "Child",
branchRef: "feature/child",
parentLaneId: parent.id,
baseRef: "main",
worktreePath: "/tmp/child",
});

expect(defaultPrTitleForLane(child, [parent, child])).toBe("Child -> Parent");
});
});

describe("right pane context defaults", () => {
function laneForContext(overrides: Partial<LaneSummary> = {}): LaneSummary {
return {
Expand Down
3 changes: 3 additions & 0 deletions apps/ade-cli/src/tuiClient/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type FileChangeEntry = {
status: WorkToolStatus;
additions: number;
deletions: number;
diff: string;
deleted?: boolean;
};

Expand Down Expand Up @@ -282,6 +283,7 @@ function appendFileChangeEvent(
existing.kind = event.kind;
existing.additions = additions;
existing.deletions = deletions;
existing.diff = event.diff;
if (deleted) existing.deleted = true;
return;
}
Expand All @@ -292,6 +294,7 @@ function appendFileChangeEvent(
status,
additions,
deletions,
diff: event.diff,
};
if (deleted) entry.deleted = true;
block.entries.push(entry);
Expand Down
Loading