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
21 changes: 0 additions & 21 deletions apps/ade-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ ade git user-identity --lane lane-id --text
ade prs create --lane lane-id --base main --title "Fix checkout flow"
ade prs list-open --text
ade prs path-to-merge --pr pr-id --model gpt-5.5 --max-rounds 3 --no-auto-merge
ade prs path-to-merge --pr pr-id --model gpt-5.5 --conflict-strategy auto --force-finalize conditional
ade prs pipeline pr-id save --conflict-strategy rebase --no-early-merge-on-green
ade run defs --text
ade run start web --lane lane-id
ade shell start --lane lane-id -- npm test
Expand All @@ -83,25 +81,6 @@ ade cursor cloud me

Use typed commands first. They validate common arguments and provide stable JSON fields or readable text summaries. Use `ade help <command> <subcommand>` for exact flags, `ade actions list --text` to discover the full service-backed action catalog, and `ade actions run <domain.action>` only when there is no typed command for the workflow yet.

The `prs path-to-merge` and `prs pipeline save` commands persist a partial `PipelineSettings` patch via `issue_inventory.savePipelineSettings` before launching the resolver. The Path to Merge orchestrator reads these from saved settings, so the same flags work either way:

| Flag | PipelineSettings field | Values |
| --- | --- | --- |
| `--max-rounds <n>` (alias `--rounds`) | `maxRounds` | positive integer |
| `--auto-merge` / `--no-auto-merge` | `autoMerge` | boolean |
| `--merge-method <m>` | `mergeMethod` | `repo_default` \| `merge` \| `squash` \| `rebase` |
| `--conflict-strategy <s>` | `conflictStrategy` | `pause` \| `rebase` \| `merge` \| `auto` |
| `--force-finalize <m>` | `forceFinalizeMode` | `off` \| `conditional` \| `unconditional` |
| `--force-finalize-require-no-ci` / `--force-finalize-allow-ci` | `forceFinalizeRequireNoCiFailures` | boolean |
| `--early-merge-on-green` / `--no-early-merge-on-green` | `earlyMergeOnGreen` | boolean |

To set fields without a dedicated flag (for example `autoAgentSettings`), call the action directly:

```bash
ade actions run issue_inventory.savePipelineSettings --args-list-json \
'["pr-1",{"autoAgentSettings":{"provider":"claude","model":"sonnet","reasoningEffort":"high","permissionMode":"guarded_edit","confidenceThreshold":0.7}}]'
```

Output modes are explicit:

```bash
Expand Down
66 changes: 0 additions & 66 deletions apps/ade-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,72 +233,6 @@ describe("ADE CLI", () => {
});
});

it("forwards new pipeline-settings flags through Path to Merge", () => {
const plan = buildCliPlan([
"prs",
"path-to-merge",
"pr-2",
"--model",
"gpt-5.4",
"--conflict-strategy",
"auto",
"--force-finalize",
"conditional",
"--no-early-merge-on-green",
]);
expect(plan.kind).toBe("execute");
if (plan.kind !== "execute") return;
expect(plan.steps).toHaveLength(2);
expect(plan.steps[0]?.params).toEqual({
name: "run_ade_action",
arguments: {
domain: "issue_inventory",
action: "savePipelineSettings",
argsList: [
"pr-2",
{
conflictStrategy: "auto",
forceFinalizeMode: "conditional",
earlyMergeOnGreen: false,
},
],
},
});
expect(plan.steps[1]?.params).toEqual({
name: "pr_start_issue_resolution",
arguments: {
prId: "pr-2",
scope: "both",
modelId: "gpt-5.4",
},
});
});

it("rejects invalid pipeline-settings enum values", () => {
expect(() =>
buildCliPlan([
"prs",
"path-to-merge",
"pr-3",
"--model",
"gpt-5.4",
"--conflict-strategy",
"wat",
]),
).toThrow(/--conflict-strategy must be one of/);
expect(() =>
buildCliPlan([
"prs",
"path-to-merge",
"pr-3",
"--model",
"gpt-5.4",
"--force-finalize",
"always",
]),
).toThrow(/--force-finalize must be one of/);
});

it("validates required arguments before service execution", () => {
expect(() => buildCliPlan(["lanes", "create"])).toThrow(/name is required/);
expect(() => buildCliPlan(["lanes", "child", "--name", "child"])).toThrow(/parent lane is required/);
Expand Down
93 changes: 16 additions & 77 deletions apps/ade-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,6 @@ const HELP_BY_COMMAND: Record<string, string> = {
$ ade prs comments <pr> --text Show unresolved review work
$ ade prs inventory <pr> Refresh ADE issue inventory
$ ade prs path-to-merge <pr> --model <model> --max-rounds 3 --no-auto-merge
$ ade prs path-to-merge <pr> --model <model> --conflict-strategy auto --force-finalize conditional
$ ade prs path-to-merge <pr> --model <model> --no-early-merge-on-green
$ ade prs pipeline <pr> save --conflict-strategy rebase --early-merge-on-green
$ ade prs resolve-thread <pr> --thread <id> Resolve a review thread
$ ade prs labels set <pr> ready-to-merge Replace labels
$ ade prs reviewers request <pr> alice bob Request reviewers
Expand Down Expand Up @@ -1337,72 +1334,6 @@ function maybePut(target: JsonObject, key: string, value: unknown): void {
}
}

/**
* Parse the PR pipeline-settings flags shared by `prs path-to-merge` and
* `prs pipeline` subcommands. Returns a partial `PipelineSettings` patch
* suitable for `issue_inventory.savePipelineSettings`. Only fields the user
* explicitly passed are included so the patch never clobbers other settings.
*
* The orchestrator reads these from saved settings (not StartPathToMergeArgs),
* so the path-to-merge command must save them before launching the loop.
*/
function readPipelineSettingsPatch(args: string[]): JsonObject {
const patch: JsonObject = {};

const maxRounds = readIntOption(args, ["--max-rounds", "--rounds"]);
if (maxRounds != null) patch.maxRounds = maxRounds;

const autoMerge = readFlag(args, ["--auto-merge"]);
const noAutoMerge = readFlag(args, ["--no-auto-merge"]);
if (autoMerge || noAutoMerge) patch.autoMerge = autoMerge && !noAutoMerge;

const mergeMethod = readValue(args, ["--merge-method"]);
if (mergeMethod) patch.mergeMethod = mergeMethod;

const conflictStrategy = readValue(args, ["--conflict-strategy"]);
if (conflictStrategy) {
if (
conflictStrategy !== "pause"
&& conflictStrategy !== "rebase"
&& conflictStrategy !== "merge"
&& conflictStrategy !== "auto"
) {
throw new CliUsageError(
"--conflict-strategy must be one of pause, rebase, merge, or auto.",
);
}
patch.conflictStrategy = conflictStrategy;
}

const forceFinalize = readValue(args, ["--force-finalize"]);
if (forceFinalize) {
if (
forceFinalize !== "off"
&& forceFinalize !== "conditional"
&& forceFinalize !== "unconditional"
) {
throw new CliUsageError(
"--force-finalize must be one of off, conditional, or unconditional.",
);
}
patch.forceFinalizeMode = forceFinalize;
}

const requireNoCi = readFlag(args, ["--force-finalize-require-no-ci"]);
const allowCi = readFlag(args, ["--force-finalize-allow-ci"]);
if (requireNoCi || allowCi) {
patch.forceFinalizeRequireNoCiFailures = requireNoCi && !allowCi;
}

const earlyMergeOn = readFlag(args, ["--early-merge-on-green"]);
const earlyMergeOff = readFlag(args, ["--no-early-merge-on-green"]);
if (earlyMergeOn || earlyMergeOff) {
patch.earlyMergeOnGreen = earlyMergeOn && !earlyMergeOff;
}

return patch;
}

function parseCliArgs(argv: string[]): ParsedCli {
const command: string[] = [];
const options: GlobalOptions = {
Expand Down Expand Up @@ -1976,16 +1907,19 @@ function buildPrPlan(args: string[]): CliPlan {
maybePut(input, "reasoning", readValue(args, ["--reasoning"]));
maybePut(input, "permissionMode", readValue(args, ["--permission-mode", "--permissions"]));
maybePut(input, "additionalInstructions", readValue(args, ["--instructions", "--additional-instructions"]));
// Path to Merge orchestrator reads conflictStrategy / forceFinalizeMode /
// earlyMergeOnGreen / autoMerge / maxRounds / mergeMethod from saved
// PipelineSettings, not from the launch args. Persist any user-supplied
// overrides before the resolver step so the loop picks them up.
const pipelinePatch = readPipelineSettingsPatch(args);
const maxRounds = readIntOption(args, ["--max-rounds", "--rounds"]);
const autoMerge = readFlag(args, ["--auto-merge"]);
const noAutoMerge = readFlag(args, ["--no-auto-merge"]);
const mergeMethod = readValue(args, ["--merge-method"]);
const steps: InvocationStep[] = [];
if (Object.keys(pipelinePatch).length > 0) {
if (maxRounds != null || autoMerge || noAutoMerge || mergeMethod) {
steps.push(actionArgsListStep("pipelineSettings", "issue_inventory", "savePipelineSettings", [
id,
pipelinePatch,
{
...(maxRounds != null ? { maxRounds } : {}),
...(autoMerge || noAutoMerge ? { autoMerge: autoMerge && !noAutoMerge } : {}),
...(mergeMethod ? { mergeMethod } : {}),
},
]));
}
steps.push(actionCallStep("result", mode === "preview" ? "pr_preview_issue_resolution_prompt" : "pr_start_issue_resolution", collectGenericObjectArgs(args, input)));
Expand All @@ -1997,7 +1931,12 @@ function buildPrPlan(args: string[]): CliPlan {
const id = requireValue(prId ?? firstPositional(args), "prId");
if (mode === "get") return { kind: "execute", label: "PR pipeline", steps: [actionArgsListStep("result", "issue_inventory", "getPipelineSettings", [id])] };
if (mode === "delete") return { kind: "execute", label: "PR pipeline delete", steps: [actionArgsListStep("result", "issue_inventory", "deletePipelineSettings", [id])] };
const settings = collectGenericObjectArgs(args, readPipelineSettingsPatch(args));
const maxRounds = readIntOption(args, ["--max-rounds", "--rounds"]);
const mergeMethod = readValue(args, ["--merge-method"]);
const settings = collectGenericObjectArgs(args, {
...(maxRounds != null ? { maxRounds } : {}),
...(mergeMethod ? { mergeMethod } : {}),
});
return { kind: "execute", label: "PR pipeline save", steps: [actionArgsListStep("result", "issue_inventory", "savePipelineSettings", [id, settings])] };
}

Expand Down
25 changes: 0 additions & 25 deletions apps/desktop/src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import { createPrService } from "./services/prs/prService";
import { createPrPollingService } from "./services/prs/prPollingService";
import { createQueueLandingService } from "./services/prs/queueLandingService";
import { createIssueInventoryService } from "./services/prs/issueInventoryService";
import { createPathToMergeOrchestrator } from "./services/prs/pathToMergeOrchestrator";
import { createPrSummaryService } from "./services/prs/prSummaryService";
import {
detectDefaultBaseRef,
Expand Down Expand Up @@ -2425,27 +2424,6 @@ app.whenReady().then(async () => {
// Wire agentChatService into prService for integration resolution
prService.setAgentChatService(agentChatService);

const pathToMergeOrchestrator = createPathToMergeOrchestrator({
logger,
prService,
laneService,
agentChatService,
sessionService,
issueInventoryService,
conflictService,
defaultModelId: null,
defaultReasoningEffort: null,
});
setImmediate(() => {
try {
pathToMergeOrchestrator.resumeFromPersistedState();
} catch (err) {
logger.warn("path_to_merge.resume_failed", {
error: err instanceof Error ? err.message : String(err),
});
}
});

const gitService = createGitOperationsService({
laneService,
operationService,
Expand Down Expand Up @@ -2947,7 +2925,6 @@ app.whenReady().then(async () => {
conflictService,
prService,
issueInventoryService,
pathToMergeOrchestrator,
queueLandingService,
sessionService,
ptyService,
Expand Down Expand Up @@ -3786,7 +3763,6 @@ app.whenReady().then(async () => {
appControlService,
queueLandingService,
issueInventoryService,
pathToMergeOrchestrator,
prSummaryService,
reviewService,
jobEngine,
Expand Down Expand Up @@ -3898,7 +3874,6 @@ app.whenReady().then(async () => {
prPollingService: null,
queueLandingService: null,
issueInventoryService: null,
pathToMergeOrchestrator: null,
prSummaryService: null,
reviewService: null,
jobEngine: null,
Expand Down
50 changes: 0 additions & 50 deletions apps/desktop/src/main/services/ipc/registerIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,6 @@ import type { createPrService } from "../prs/prService";
import type { createPrPollingService } from "../prs/prPollingService";
import type { createQueueLandingService } from "../prs/queueLandingService";
import type { createIssueInventoryService } from "../prs/issueInventoryService";
import type { PathToMergeOrchestrator } from "../prs/pathToMergeOrchestrator";
import type { createPrSummaryService } from "../prs/prSummaryService";
import type { createReviewService } from "../review/reviewService";
import type { createAgentChatService } from "../chat/agentChatService";
Expand Down Expand Up @@ -692,7 +691,6 @@ export type AppContext = {
prPollingService: ReturnType<typeof createPrPollingService>;
queueLandingService: ReturnType<typeof createQueueLandingService>;
issueInventoryService: ReturnType<typeof createIssueInventoryService>;
pathToMergeOrchestrator?: PathToMergeOrchestrator | null;
prSummaryService: ReturnType<typeof createPrSummaryService>;
reviewService: ReturnType<typeof createReviewService>;
jobEngine: ReturnType<typeof createJobEngine>;
Expand Down Expand Up @@ -7276,11 +7274,6 @@ export function registerIpc({
return await ctx.prService.landStack(arg);
});

ipcMain.handle(IPC.prsRetargetBase, async (_event, arg: { prId: string; baseBranch: string }): Promise<void> => {
const ctx = getCtx();
return await ctx.prService.retargetBase(arg.prId, arg.baseBranch);
});

ipcMain.handle(IPC.prsOpenInGitHub, async (_event, arg: { prId: string }): Promise<void> => {
const ctx = getCtx();
return await ctx.prService.openInGitHub(arg.prId);
Expand Down Expand Up @@ -7876,49 +7869,6 @@ export function registerIpc({
ipcMain.handle(IPC.prsConvergenceStateDelete, (_e, args: { prId: string }): void =>
getCtx().issueInventoryService.resetConvergenceRuntime(args.prId));

ipcMain.handle(
IPC.prsPathToMergeStart,
async (_e, args: {
prId: string;
modelId?: string | null;
reasoning?: string | null;
scope?: "checks" | "comments" | "both";
additionalInstructions?: string | null;
}) => {
const orchestrator = getCtx().pathToMergeOrchestrator;
if (!orchestrator) {
throw new Error("Path to Merge orchestrator is not available in this build.");
}
const prId = typeof args?.prId === "string" ? args.prId.trim() : "";
if (!prId) throw new Error("prId is required");
return await orchestrator.startPathToMerge({
prId,
modelId: typeof args?.modelId === "string" ? args.modelId : null,
reasoning: typeof args?.reasoning === "string" ? args.reasoning : null,
scope: args?.scope === "checks" || args?.scope === "comments" || args?.scope === "both"
? args.scope
: undefined,
additionalInstructions: typeof args?.additionalInstructions === "string" ? args.additionalInstructions : null,
});
},
);

ipcMain.handle(
IPC.prsPathToMergeStop,
async (_e, args: { prId: string; reason?: string | null }) => {
const orchestrator = getCtx().pathToMergeOrchestrator;
if (!orchestrator) {
throw new Error("Path to Merge orchestrator is not available in this build.");
}
const prId = typeof args?.prId === "string" ? args.prId.trim() : "";
if (!prId) throw new Error("prId is required");
return await orchestrator.stopPathToMerge({
prId,
reason: typeof args?.reason === "string" ? args.reason : null,
});
},
);

ipcMain.handle(IPC.prsPipelineSettingsGet, (_e, args: { prId: string }): PipelineSettings =>
getCtx().issueInventoryService.getPipelineSettings(args.prId));
ipcMain.handle(IPC.prsPipelineSettingsSave, (_e, args: { prId: string; settings: Partial<PipelineSettings> }): void =>
Expand Down
Loading
Loading