Skip to content

Commit 56a8469

Browse files
arul28claude
andcommitted
ship: continuation of ADE-29 lane management work
Picks up uncommitted lane work across desktop, ade-cli, ios, and docs: ManageLaneDialog refactor (singleLane), chat pane turn-active error handling, iOS sync command helpers (lanes.reparent), sync host / remote command updates, project transition state, PR detail/route state cleanup, and broad doc refreshes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ee677bc commit 56a8469

56 files changed

Lines changed: 2866 additions & 874 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/ade-cli/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ ade init
229229
ade lanes list --text
230230
ade lanes create "fix-checkout-flow" --parent main
231231
ade lanes create "lin-123" --linear-issue-json '{"id":"...","identifier":"LIN-123","title":"...","projectId":"...","projectSlug":"...","teamId":"...","teamKey":"...","stateId":"...","stateName":"Todo","stateType":"unstarted","priority":2,"priorityLabel":"high","labels":[],"assigneeId":null,"assigneeName":null,"createdAt":"...","updatedAt":"..."}'
232+
ade lanes reparent lane-child --parent lane-parent --stack-base-branch main
232233
ade --role cto linear quick-view --text
233234
ade --role cto linear search-issues --query "auth" --state-type started,unstarted --first 50
234235
ade git commit --lane lane-id
@@ -239,6 +240,7 @@ ade diff patch --lane lane-id --path src/file.ts --text
239240
ade prs create --lane lane-id --base main --title "Fix checkout flow"
240241
ade prs create --lane lane-id --base main --close-linear-issue-on-merge
241242
ade prs list-open --text
243+
ade prs github-snapshot --include-external-closed
242244
ade prs path-to-merge --pr pr-id --model gpt-5.5 --max-rounds 3 --no-auto-merge
243245
ade prs path-to-merge --pr pr-id --model gpt-5.5 --conflict-strategy auto --force-finalize conditional
244246
ade prs pipeline pr-id save --conflict-strategy rebase --no-early-merge-on-green

apps/ade-cli/src/adeRpcServer.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ function createRuntime() {
337337
void data;
338338
return true;
339339
}),
340+
readTranscriptTail: vi.fn(async () => ""),
340341
enrichSessions: vi.fn((sessions: unknown[]) => sessions),
341342
},
342343
testService: {

apps/ade-cli/src/adeRpcServer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3592,9 +3592,11 @@ async function waitForSessionCompletion(args: {
35923592
while (Date.now() <= deadline) {
35933593
const session = runtime.sessionService.get(sessionId);
35943594
if (session && session.status !== "running") {
3595-
const logTail = runtime.sessionService.readTranscriptTail(session.transcriptPath, maxLogBytes, {
3595+
const logTail = await runtime.ptyService.readTranscriptTail({
3596+
sessionId,
3597+
maxBytes: maxLogBytes,
35963598
raw: true,
3597-
alignToLineBoundary: true
3599+
alignToLineBoundary: true,
35983600
});
35993601
return {
36003602
session,
@@ -3610,9 +3612,11 @@ async function waitForSessionCompletion(args: {
36103612
session,
36113613
timedOut: true,
36123614
logTail: session
3613-
? runtime.sessionService.readTranscriptTail(session.transcriptPath, maxLogBytes, {
3615+
? await runtime.ptyService.readTranscriptTail({
3616+
sessionId,
3617+
maxBytes: maxLogBytes,
36143618
raw: true,
3615-
alignToLineBoundary: true
3619+
alignToLineBoundary: true,
36163620
})
36173621
: ""
36183622
};

apps/ade-cli/src/cli.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,74 @@ describe("ADE CLI", () => {
15021502
});
15031503
});
15041504

1505+
it("forwards lane reparent stack base branch override to the runtime action", () => {
1506+
const reparent = buildCliPlan([
1507+
"lanes",
1508+
"reparent",
1509+
"lane-child",
1510+
"--parent",
1511+
"lane-parent",
1512+
"--stack-base-branch",
1513+
"develop",
1514+
]);
1515+
expect(reparent.kind).toBe("execute");
1516+
if (reparent.kind !== "execute") return;
1517+
expect(reparent.steps[0]?.params).toEqual({
1518+
name: "run_ade_action",
1519+
arguments: {
1520+
domain: "lane",
1521+
action: "reparent",
1522+
args: {
1523+
laneId: "lane-child",
1524+
newParentLaneId: "lane-parent",
1525+
stackBaseBranchRef: "develop",
1526+
},
1527+
},
1528+
});
1529+
1530+
const reparentDefault = buildCliPlan([
1531+
"lanes",
1532+
"reparent",
1533+
"lane-child",
1534+
"--parent",
1535+
"lane-parent",
1536+
]);
1537+
expect(reparentDefault.kind).toBe("execute");
1538+
if (reparentDefault.kind !== "execute") return;
1539+
expect(reparentDefault.steps[0]?.params).toEqual({
1540+
name: "run_ade_action",
1541+
arguments: {
1542+
domain: "lane",
1543+
action: "reparent",
1544+
args: {
1545+
laneId: "lane-child",
1546+
newParentLaneId: "lane-parent",
1547+
},
1548+
},
1549+
});
1550+
});
1551+
1552+
it("forwards PR GitHub snapshot full-history flag to the runtime action", () => {
1553+
const snapshot = buildCliPlan([
1554+
"prs",
1555+
"github-snapshot",
1556+
"--include-external-closed",
1557+
]);
1558+
expect(snapshot.kind).toBe("execute");
1559+
if (snapshot.kind !== "execute") return;
1560+
expect(snapshot.steps[0]?.params).toEqual({
1561+
name: "run_ade_action",
1562+
arguments: {
1563+
domain: "pr",
1564+
action: "getGithubSnapshot",
1565+
args: {
1566+
force: false,
1567+
includeExternalClosed: true,
1568+
},
1569+
},
1570+
});
1571+
});
1572+
15051573
it("maps discoverable git status, sync, and conflict helpers to existing actions", () => {
15061574
const fullStatus = buildCliPlan([
15071575
"git",
@@ -1854,6 +1922,16 @@ describe("ADE CLI", () => {
18541922
// Regression: --text as output flag must not swallow --help.
18551923
const lanesHelp = buildCliPlan(["lanes", "list", "--text", "--help"]);
18561924
expect(lanesHelp.kind).toBe("help");
1925+
1926+
const reparentHelp = buildCliPlan([
1927+
"lanes",
1928+
"reparent",
1929+
"lane-child",
1930+
"--stack-base-branch",
1931+
"develop",
1932+
"--help",
1933+
]);
1934+
expect(reparentHelp.kind).toBe("help");
18571935
});
18581936

18591937
it("maps PR create Linear close flag to the typed RPC tool", () => {

apps/ade-cli/src/cli.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,9 @@ const HELP_BY_COMMAND: Record<string, string> = {
882882
$ ade lanes archive <lane> Archive a lane in ADE
883883
$ ade lanes unarchive <lane> Restore an archived lane
884884
$ ade lanes attach --path <worktree> --name <n> Attach an external worktree
885+
$ ade lanes reparent <lane> --parent <parent> Move lane onto a new parent (runs git rebase)
886+
$ ade lanes reparent <lane> --parent <parent> --stack-base-branch <branch>
887+
Reparent and stack onto a specific branch (e.g. origin/main)
885888
$ ade lanes actions --text List callable lane service methods
886889
`,
887890
git: `${ADE_BANNER}
@@ -947,6 +950,8 @@ const HELP_BY_COMMAND: Record<string, string> = {
947950
$ ade prs link --lane <lane> --url <pr-url> Map an existing GitHub PR to a lane
948951
$ ade prs checks <pr> --text Show check status
949952
$ ade prs comments <pr> --text Show unresolved review work
953+
$ ade prs github-snapshot --include-external-closed
954+
Include closed external PR history in the GitHub snapshot
950955
$ ade prs inventory <pr> Refresh ADE issue inventory
951956
$ ade prs path-to-merge <pr> --model <model> --max-rounds 3 --no-auto-merge
952957
$ ade prs path-to-merge <pr> --model <model> --conflict-strategy auto --force-finalize conditional
@@ -2478,6 +2483,23 @@ function buildLanePlan(args: string[]): CliPlan {
24782483
readLaneId(args) ?? firstPositional(args),
24792484
"laneId",
24802485
);
2486+
const reparentArgs: JsonObject = {
2487+
laneId,
2488+
newParentLaneId:
2489+
readValue(args, [
2490+
"--parent",
2491+
"--parent-lane",
2492+
"--parent-lane-id",
2493+
]) ?? firstPositional(args),
2494+
};
2495+
const stackBaseBranchRef = readValue(args, [
2496+
"--stack-base-branch",
2497+
"--stack-base",
2498+
"--base-branch-ref",
2499+
]);
2500+
if (stackBaseBranchRef != null) {
2501+
reparentArgs.stackBaseBranchRef = stackBaseBranchRef;
2502+
}
24812503
return {
24822504
kind: "execute",
24832505
label: "lane reparent",
@@ -2486,15 +2508,7 @@ function buildLanePlan(args: string[]): CliPlan {
24862508
"result",
24872509
"lane",
24882510
"reparent",
2489-
collectGenericObjectArgs(args, {
2490-
laneId,
2491-
newParentLaneId:
2492-
readValue(args, [
2493-
"--parent",
2494-
"--parent-lane",
2495-
"--parent-lane-id",
2496-
]) ?? firstPositional(args),
2497-
}),
2511+
collectGenericObjectArgs(args, reparentArgs),
24982512
),
24992513
],
25002514
};
@@ -3628,7 +3642,13 @@ function buildPrPlan(args: string[]): CliPlan {
36283642
label: "PR mobile snapshot",
36293643
steps: [actionArgsListStep("result", "pr", "getMobileSnapshot", [])],
36303644
};
3631-
if (sub === "github-snapshot")
3645+
if (sub === "github-snapshot") {
3646+
const snapshotArgs: JsonObject = {
3647+
force: readFlag(args, ["--force"]),
3648+
};
3649+
if (readFlag(args, ["--include-external-closed", "--include-closed-external"])) {
3650+
snapshotArgs.includeExternalClosed = true;
3651+
}
36323652
return {
36333653
kind: "execute",
36343654
label: "PR GitHub snapshot",
@@ -3637,12 +3657,11 @@ function buildPrPlan(args: string[]): CliPlan {
36373657
"result",
36383658
"pr",
36393659
"getGithubSnapshot",
3640-
collectGenericObjectArgs(args, {
3641-
force: readFlag(args, ["--force"]),
3642-
}),
3660+
collectGenericObjectArgs(args, snapshotArgs),
36433661
),
36443662
],
36453663
};
3664+
}
36463665
if (sub === "conflicts") {
36473666
const mode = firstPositional(args) ?? "list";
36483667
if (mode === "list")
@@ -8312,6 +8331,7 @@ const VALUE_CARRIER_FLAGS: ReadonlySet<string> = new Set([
83128331
"--backend",
83138332
"--base",
83148333
"--base-branch",
8334+
"--base-branch-ref",
83158335
"--base-ref",
83168336
"--body",
83178337
"--branch",
@@ -8449,6 +8469,8 @@ const VALUE_CARRIER_FLAGS: ReadonlySet<string> = new Set([
84498469
"--source",
84508470
"--source-lane",
84518471
"--stack",
8472+
"--stack-base",
8473+
"--stack-base-branch",
84528474
"--stack-id",
84538475
"--scheme",
84548476
"--start-point",

apps/ade-cli/src/services/sync/syncHostService.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ function createHostArgs(projectRoot: string, projects: SyncMobileProjectSummary[
292292
},
293293
ptyService: {
294294
create: vi.fn(),
295+
readTranscriptTail: vi.fn(async () => ""),
295296
enrichSessions: (rows: unknown[]) => rows,
296297
},
297298
computerUseArtifactBrokerService: {

apps/ade-cli/src/services/sync/syncHostService.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2830,11 +2830,12 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
28302830
peer.subscribedSessionIds.add(sessionId);
28312831
const session = args.sessionService.get(sessionId);
28322832
const transcript = session
2833-
? await args.sessionService.readTranscriptTail(
2834-
session.transcriptPath,
2835-
Math.max(1_024, Math.min(2_000_000, Math.floor(payload?.maxBytes ?? DEFAULT_TERMINAL_SNAPSHOT_BYTES))),
2836-
{ raw: true, alignToLineBoundary: true },
2837-
)
2833+
? await args.ptyService.readTranscriptTail({
2834+
sessionId,
2835+
maxBytes: Math.max(1_024, Math.min(2_000_000, Math.floor(payload?.maxBytes ?? DEFAULT_TERMINAL_SNAPSHOT_BYTES))),
2836+
raw: true,
2837+
alignToLineBoundary: true,
2838+
})
28382839
: "";
28392840
const snapshot: SyncTerminalSnapshotPayload = {
28402841
sessionId,

apps/ade-cli/src/services/sync/syncRemoteCommandService.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -404,15 +404,12 @@ function parseRenameLaneArgs(value: Record<string, unknown>): RenameLaneArgs {
404404
}
405405

406406
function parseReparentLaneArgs(value: Record<string, unknown>): ReparentLaneArgs {
407-
const parsed: ReparentLaneArgs = {
407+
const stackBaseBranchRef = asTrimmedString(value.stackBaseBranchRef);
408+
return {
408409
laneId: requireString(value.laneId, "lanes.reparent requires laneId."),
409410
newParentLaneId: requireString(value.newParentLaneId, "lanes.reparent requires newParentLaneId."),
411+
...(stackBaseBranchRef ? { stackBaseBranchRef } : {}),
410412
};
411-
const stackBase = asTrimmedString(value.stackBaseBranchRef);
412-
if (stackBase) {
413-
parsed.stackBaseBranchRef = stackBase;
414-
}
415-
return parsed;
416413
}
417414

418415
function parseUpdateLaneAppearanceArgs(value: Record<string, unknown>): UpdateLaneAppearanceArgs {
@@ -2338,7 +2335,10 @@ export function createSyncRemoteCommandService(args: SyncRemoteCommandServiceArg
23382335
register("prs.getComments", { viewerAllowed: true }, async (payload) => args.prService.getComments(requirePrId(payload, "prs.getComments")));
23392336
register("prs.getFiles", { viewerAllowed: true }, async (payload) => args.prService.getFiles(requirePrId(payload, "prs.getFiles")));
23402337
register("prs.getGitHubSnapshot", { viewerAllowed: true }, async (payload) =>
2341-
args.prService.getGithubSnapshot({ force: payload.force === true }));
2338+
args.prService.getGithubSnapshot({
2339+
force: payload.force === true,
2340+
includeExternalClosed: payload.includeExternalClosed === true,
2341+
}));
23422342
register("prs.getReviewThreads", { viewerAllowed: true }, async (payload) => args.prService.getReviewThreads(requirePrId(payload, "prs.getReviewThreads")));
23432343
register("prs.getActionRuns", { viewerAllowed: true }, async (payload) => args.prService.getActionRuns(requirePrId(payload, "prs.getActionRuns")));
23442344
register("prs.getActivity", { viewerAllowed: true }, async (payload) => args.prService.getActivity(requirePrId(payload, "prs.getActivity")));

apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,85 @@ describe("subagentSnapshotsFromEvents", () => {
174174
summary: "done",
175175
});
176176
});
177+
178+
it("adopts the resolved task id when a runtime placeholder has the same parent tool id", () => {
179+
const snapshots = subagentSnapshotsFromEvents([
180+
{
181+
sessionId: "s1",
182+
timestamp: "2026-01-01T12:00:00.000Z",
183+
sequence: 1,
184+
event: {
185+
type: "subagent_started",
186+
taskId: "spawn-1",
187+
parentToolUseId: "spawn-1",
188+
description: "Parallel agent",
189+
},
190+
},
191+
{
192+
sessionId: "s1",
193+
timestamp: "2026-01-01T12:00:01.000Z",
194+
sequence: 2,
195+
event: {
196+
type: "subagent_progress",
197+
taskId: "thread-1",
198+
parentToolUseId: "spawn-1",
199+
summary: "working",
200+
},
201+
},
202+
]);
203+
204+
expect(snapshots).toHaveLength(1);
205+
expect(snapshots[0]).toMatchObject({
206+
id: "thread-1",
207+
name: "Parallel agent",
208+
parentToolUseId: "spawn-1",
209+
status: "running",
210+
summary: "working",
211+
});
212+
});
213+
214+
it("stops foreground subagents when their parent turn has ended", () => {
215+
const snapshots = subagentSnapshotsFromEvents([
216+
{
217+
sessionId: "s1",
218+
timestamp: "2026-01-01T12:00:00.000Z",
219+
sequence: 1,
220+
event: {
221+
type: "subagent_started",
222+
taskId: "agent-1",
223+
description: "Foreground agent",
224+
turnId: "turn-1",
225+
},
226+
},
227+
{
228+
sessionId: "s1",
229+
timestamp: "2026-01-01T12:00:01.000Z",
230+
sequence: 2,
231+
event: {
232+
type: "subagent_started",
233+
taskId: "agent-bg",
234+
description: "Background agent",
235+
background: true,
236+
turnId: "turn-1",
237+
},
238+
},
239+
{
240+
sessionId: "s1",
241+
timestamp: "2026-01-01T12:00:02.000Z",
242+
sequence: 3,
243+
event: { type: "done", turnId: "turn-1", status: "completed" },
244+
},
245+
]);
246+
247+
expect(snapshots.find((snapshot) => snapshot.id === "agent-1")).toMatchObject({
248+
status: "stopped",
249+
summary: "Parent turn ended before ADE received a final subagent status",
250+
});
251+
expect(snapshots.find((snapshot) => snapshot.id === "agent-bg")).toMatchObject({
252+
status: "running",
253+
background: true,
254+
});
255+
});
177256
});
178257

179258
describe("clampChatScrollOffsetRows", () => {

0 commit comments

Comments
 (0)