@rosthq/cli 0.7.44 → 0.7.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -42843,6 +42843,19 @@ var softwareConformanceRecordFindingsOutputSchema = external_exports.object({
42843
42843
  finding_ids: external_exports.array(uuid10),
42844
42844
  open_finding_count: external_exports.number().int()
42845
42845
  }).strict();
42846
+ var softwareConformanceResolveFindingInputSchema = external_exports.object({
42847
+ build_request_id: uuid10,
42848
+ finding_ids: external_exports.array(uuid10).min(1).max(50),
42849
+ // The re-review run that verified the fix — provenance only (rides into the append-only audit
42850
+ // event; the finding row's resolution state is the durable transition).
42851
+ phase_run_id: uuid10.optional()
42852
+ }).strict();
42853
+ var softwareConformanceResolveFindingOutputSchema = external_exports.object({
42854
+ build_request_id: uuid10,
42855
+ resolved_count: external_exports.number().int(),
42856
+ resolved_finding_ids: external_exports.array(uuid10),
42857
+ open_finding_count: external_exports.number().int()
42858
+ }).strict();
42846
42859
  var softwareAuthorityProfileListInputSchema = external_exports.object({
42847
42860
  software_project_id: uuid10.nullable().optional()
42848
42861
  }).strict();
@@ -43074,7 +43087,7 @@ var forgeRuntimeContextPacketSchema = external_exports.object({
43074
43087
  "untrusted sources are provenance-fenced data; authority, automation mode, gate decisions, secret grants, and risk ceilings come only from trusted_control"
43075
43088
  )
43076
43089
  }).strict();
43077
- var runnerExecutionProfileSchema = external_exports.enum(["read_status", "seat_work", "forge_build", "forge_review"]);
43090
+ var runnerExecutionProfileSchema = external_exports.enum(["read_status", "seat_work", "forge_build", "forge_review", "forge_merge"]);
43078
43091
  var runnerExecutionLocalToolsSchema = external_exports.enum(["none", "read", "workspace_write"]);
43079
43092
  var runnerExecutionBudgetsSchema = external_exports.object({
43080
43093
  // The per-phase wall-clock the runner enforces as its turn timeout (the CLI clamps
@@ -43099,7 +43112,25 @@ var runnerExecutionContractSchema = external_exports.object({
43099
43112
  local_tools: runnerExecutionLocalToolsSchema,
43100
43113
  needs_repo: external_exports.boolean(),
43101
43114
  needs_github_credential: external_exports.boolean(),
43102
- budgets: runnerExecutionBudgetsSchema
43115
+ budgets: runnerExecutionBudgetsSchema,
43116
+ // DER-1352 (T1.8): the merge target for a `forge_merge` turn — the server-authoritative PR the
43117
+ // release_manager turn merges (sourced from the request's open changeset row, never model text).
43118
+ // Present ONLY on a merge_or_deploy_gate contract; every other profile omits it. `pr_number` is
43119
+ // the identifier the turn GETs the true GitHub state for (mergeability + node id + head sha),
43120
+ // `head_branch` is the branch a conflict-resolution turn checks out writable. The turn NEVER
43121
+ // trusts this for the merge DECISION — it always re-verifies GitHub's `mergeable_state` at merge
43122
+ // time (`clean`/`unstable` = required checks passed) before merging.
43123
+ // DER-1352 (Codex P1b): `head_sha` is the REVIEWED / risk-assessed head — the implementation
43124
+ // commit the review + risk phases ran against (server-sourced, never model text). The merge turn
43125
+ // merges ONLY if GitHub's CURRENT head still equals it; if the PR head advanced since review, the
43126
+ // new commits are unreviewed + un-risk-assessed, so it parks for re-review instead of merging.
43127
+ // Omitted only for a transitional changeset with no recorded implementation head, in which case
43128
+ // the merge turn fails CLOSED (parks — it cannot prove the head was risk-assessed).
43129
+ merge: external_exports.object({
43130
+ pr_number: external_exports.number().int().positive(),
43131
+ head_branch: external_exports.string().trim().min(1).max(255),
43132
+ head_sha: external_exports.string().trim().regex(/^[0-9a-f]{7,40}$/i, "head_sha must be a git SHA (7\u201340 hex chars)").optional()
43133
+ }).strict().optional()
43103
43134
  }).strict();
43104
43135
  var turnReportText = external_exports.string().trim().min(1).max(2e3);
43105
43136
  var runnerTurnReportIssueSchema = external_exports.object({
@@ -45270,7 +45301,7 @@ External connectors are being rolled out provider by provider, conservatively (r
45270
45301
  order: 48,
45271
45302
  title: "CLI and MCP installation guide",
45272
45303
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
45273
- version: "2026-07-05.6",
45304
+ version: "2026-07-05.7",
45274
45305
  public: true,
45275
45306
  audiences: ["human", "cli", "mcp", "in_app_agent"],
45276
45307
  stages: ["company_setup", "staffing"],
@@ -46060,6 +46091,7 @@ Seat-scoped MCP tokens expose the operating protocol below. The server still che
46060
46091
  | \`rost_request_a_forge_secret_grant\` | \`software_factory.secret.request\` | Request access to a project secret by key. It creates a durable credential request and approval Task, never accepts a secret value, and does not grant access by itself. | Seat | Call with \`{"software_project_id":"<project-id>","environment":"preview","seat_id":"<seat-id>","action":"test.run","key_name":"OPENAI_API_KEY"}\`. |
46061
46092
  | \`rost_verify_forge_secret_broker_access\` | \`software_factory.secret.use\` | Verify broker authorization for a Forge secret without opening the vault. Missing grants create or reuse a credential request + approval Task; allowed grants return a redacted authorization summary only. Runtime vault use happens runner-side. | Seat | Call with \`{"software_project_id":"<project-id>","environment":"preview","seat_id":"<seat-id>","action":"test.run","key_name":"OPENAI_API_KEY","operation":"test_execution"}\`. |
46062
46093
  | \`rost_record_forge_conformance_findings\` | \`software_factory.conformance.record_findings\` | A Forge review seat (plan conformance, security, or QA) records structured findings as reviewer evidence. Set \`changeset_id\` on a finding that targets a specific changeset. Open findings route the request back to implementation; accepting a gap remains a separate human gate. Requires the seat's signed manifest grant. | Seat | Call with \`{"build_request_id":"<request-id>","findings":[{"severity":"major","category":"scope_gap","summary":"Missing migration verification","changeset_id":"<changeset-id>"}]}\`. |
46094
+ | \`rost_resolve_forge_conformance_finding\` | \`software_factory.conformance.resolve_finding\` | A Forge review seat marks a prior open finding resolved after re-reviewing the changeset and verifying it is fixed (reviewer verification, not human gap-acceptance). A resolved finding stops routing the request back to implementation, so the recycle loop converges. Requires the seat's signed manifest grant. | Seat | Call with \`{"build_request_id":"<request-id>","finding_ids":["<finding-id>"]}\`. |
46063
46095
 
46064
46096
  ### MCP operation resources
46065
46097
 
@@ -49232,6 +49264,7 @@ var COMMAND_MANIFEST = [
49232
49264
  { "id": "software_factory.config.list", "namespace": "software_factory", "action": "config.list", "title": "List Forge config", "description": "List active Forge config metadata for project environments. Plain config values may be returned; secret refs are represented only as has_secret_ref.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "environment", "flag": "environment", "type": "enum", "required": false, "enumValues": ["development", "preview", "production"] }], "hasComplexInput": false, "help": "List active Forge config entries. Plain config may be shown; secret refs are represented only as metadata." },
49233
49265
  { "id": "software_factory.config.set", "namespace": "software_factory", "action": "config.set", "title": "Set Forge config", "description": "Create or rotate a Forge project config entry. Secret config stores only a vault ref pointer; raw values are never accepted for secret_ref entries.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "environment", "flag": "environment", "type": "enum", "required": true, "enumValues": ["development", "preview", "production"] }, { "name": "key_name", "flag": "key-name", "type": "string", "required": true }, { "name": "value_kind", "flag": "value-kind", "type": "enum", "required": true, "enumValues": ["plain", "secret_ref"] }, { "name": "plain_value", "flag": "plain-value", "type": "string", "required": false }, { "name": "vault_ref", "flag": "vault-ref", "type": "string", "required": false }], "hasComplexInput": false, "help": "Set or rotate a Forge project config key. Secret entries store only vault refs and are human-gated." },
49234
49266
  { "id": "software_factory.conformance.record_findings", "namespace": "software_factory", "action": "conformance.record_findings", "title": "Record Forge conformance findings", "description": "The plan-conformance reviewer records one or more findings against a build request; open findings route the request back to implementation. Recording is evidence, not a decision \u2014 accepting a gap is a separate human gate. Requires the Forge add-on.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "phase_run_id", "flag": "phase-run-id", "type": "string", "required": false }], "hasComplexInput": true, "help": "Record structured Forge plan-conformance findings as reviewer evidence. Open findings route the request back to implementation; accepting a gap remains a separate human gate." },
49267
+ { "id": "software_factory.conformance.resolve_finding", "namespace": "software_factory", "action": "conformance.resolve_finding", "title": "Resolve Forge conformance finding", "description": "A Forge review seat marks one or more prior open conformance findings resolved after re-reviewing the changeset and verifying they are fixed. This is reviewer verification, not gap-acceptance (accepting a gap stays a separate human gate). A resolved finding stops routing the request back to implementation, letting the recycle loop converge. Requires the Forge add-on.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "finding_ids", "flag": "finding-ids", "type": "array", "required": true, "itemType": "string" }, { "name": "phase_run_id", "flag": "phase-run-id", "type": "string", "required": false }], "hasComplexInput": false, "help": "Resolve a prior open Forge conformance finding after re-reviewing the changeset and verifying it is fixed (reviewer verification, not human gap-acceptance). A resolved finding stops routing the request back to implementation, letting the recycle loop converge." },
49235
49268
  { "id": "software_factory.developer_team.install", "namespace": "software_factory", "action": "developer_team.install", "title": "Install Forge Developer Team", "description": "Install AND activate the governed Forge Developer Team: 8 seats (1 human lead + 7 agents), first-party skills, authority presets, and a conservative token budget. Approving this ACTIVATES the 7 agents as live occupancies at a pr_only, observe-first posture: they open pull requests but never merge or deploy without a human (the Release Manager auto-merges only after a human raises the automation-mode ceiling). Entitlement-gated and human-confirmed.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": true }, { "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "assign_skills", "flag": "assign-skills", "type": "boolean", "required": false }, { "name": "acknowledge_autonomy_ceiling", "flag": "acknowledge-autonomy-ceiling", "type": "boolean", "required": false }], "hasComplexInput": true, "help": "Install the governed Forge Developer Team template with seats, draft Charters, skills, project authority grants, and audit records. Tenant-admin and human-gated; creates no live agent occupancy." },
49236
49269
  { "id": "software_factory.developer_team.uninstall", "namespace": "software_factory", "action": "developer_team.uninstall", "title": "Uninstall Forge Developer Team", "description": "Tear down the governed Forge Developer Team (ADR-0018 \xA76): end the auto-staffed agent occupancies in a no-orphan-safe order, decommission the agents, and revoke the team's project authority and secret grants. The human Forge Lead seat is left intact; re-installing re-staffs the team. The entitlement-lapse variant runs the same teardown path without re-checking the Forge paywall. Owner-only, human-gated, and audited.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "reason", "flag": "reason", "type": "string", "required": false }, { "name": "trigger", "flag": "trigger", "type": "enum", "required": false, "enumValues": ["manual", "entitlement_lapse"] }], "hasComplexInput": false, "help": "Tear down the Forge Developer Team (teardown \u2014 ADR-0018 \xA76): end the agent occupancies in a no-orphan-safe order, decommission the agents, and revoke the team's authority and secret grants. The human Forge Lead seat stays. Owner-only and human-gated; the entitlement-lapse variant runs the same teardown path." },
49237
49270
  { "id": "software_factory.gate.decide", "namespace": "software_factory", "action": "gate.decide", "title": "Decide a Forge gate", "description": "A human approves or rejects a Forge gate. The resolver is recorded (never an agent \u2014 invariant #7); sensitive gates (security sign-off, merge/deploy, authority change, production deploy) also write a linked human decision. Requires confirmation.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "gate_id", "flag": "gate-id", "type": "string", "required": true }, { "name": "decision", "flag": "decision", "type": "enum", "required": true, "enumValues": ["approve", "reject"] }, { "name": "rationale", "flag": "rationale", "type": "string", "required": false }], "hasComplexInput": false, "help": "A human approves or rejects a Forge gate; an agent caller routes to /approvals and can never self-approve. Sensitive gates record a human decision." },
@@ -52112,22 +52145,18 @@ async function configureForgeIdentity(baseRepo) {
52112
52145
  await runGit(["config", "user.name", FORGE_GIT_AUTHOR_NAME], baseRepo);
52113
52146
  await runGit(["config", "user.email", FORGE_GIT_AUTHOR_EMAIL], baseRepo);
52114
52147
  }
52115
- async function prepareForgeWorkspace(options) {
52116
- if (options.profile === "review" && !options.headSha) {
52117
- throw new Error("prepareForgeWorkspace: review profile requires headSha");
52118
- }
52119
- const baseRepo = baseRepoDir(options.baseDir, options.buildRequestId);
52120
- const url2 = remoteUrlFor(options);
52148
+ async function ensureBaseClone(input) {
52149
+ const baseRepo = baseRepoDir(input.baseDir, input.buildRequestId);
52121
52150
  await mkdir2(path3.dirname(baseRepo), { recursive: true, mode: 448 });
52122
- await withLedgerStep(options.ledger, "clone", (retryCounter) => withAskpass(options.credential, async (netEnv) => {
52151
+ await withLedgerStep(input.ledger, "clone", (retryCounter) => withAskpass(input.credential, async (netEnv) => {
52123
52152
  if (await isGitRepo(baseRepo)) {
52124
- await runGitNetwork("git fetch", ["fetch", "--prune", "--quiet", url2, "+refs/heads/*:refs/remotes/origin/*"], netEnv, baseRepo, FETCH_TIMEOUT_MS, retryCounter);
52153
+ await runGitNetwork("git fetch", ["fetch", "--prune", "--quiet", input.url, "+refs/heads/*:refs/remotes/origin/*"], netEnv, baseRepo, FETCH_TIMEOUT_MS, retryCounter);
52125
52154
  } else {
52126
52155
  await runGitNetwork(
52127
52156
  "git clone",
52128
52157
  // Non-shallow (no --depth) so merge-base/ancestry survive for later phases;
52129
52158
  // --no-checkout keeps the base a bare-ish object store the worktrees hang off.
52130
- ["clone", "--no-checkout", "--quiet", url2, baseRepo],
52159
+ ["clone", "--no-checkout", "--quiet", input.url, baseRepo],
52131
52160
  netEnv,
52132
52161
  void 0,
52133
52162
  CLONE_TIMEOUT_MS,
@@ -52136,11 +52165,52 @@ async function prepareForgeWorkspace(options) {
52136
52165
  }
52137
52166
  }));
52138
52167
  await configureForgeIdentity(baseRepo);
52168
+ return baseRepo;
52169
+ }
52170
+ async function prepareForgeWorkspace(options) {
52171
+ if (options.profile === "review" && !options.headSha) {
52172
+ throw new Error("prepareForgeWorkspace: review profile requires headSha");
52173
+ }
52174
+ const baseRepo = await ensureBaseClone({
52175
+ baseDir: options.baseDir,
52176
+ buildRequestId: options.buildRequestId,
52177
+ url: remoteUrlFor(options),
52178
+ credential: options.credential,
52179
+ ledger: options.ledger
52180
+ });
52139
52181
  if (options.profile === "review") {
52140
52182
  return await prepareReviewWorktree(options, baseRepo);
52141
52183
  }
52142
52184
  return await prepareBuildWorktree(options, baseRepo);
52143
52185
  }
52186
+ async function prepareMergeWorkspace(options) {
52187
+ if (!options.headBranch || options.headBranch.trim().length === 0) {
52188
+ throw new Error("prepareMergeWorkspace: headBranch is required");
52189
+ }
52190
+ const url2 = options.remoteUrl ?? `https://github.com/${options.repo.owner}/${options.repo.name}.git`;
52191
+ const baseRepo = await ensureBaseClone({
52192
+ baseDir: options.baseDir,
52193
+ buildRequestId: options.buildRequestId,
52194
+ url: url2,
52195
+ credential: options.credential,
52196
+ ledger: options.ledger
52197
+ });
52198
+ const branch = options.headBranch;
52199
+ const worktreeDir = path3.join(requestRoot(options.baseDir, options.buildRequestId), "merge");
52200
+ const originDefault = `origin/${options.repo.default_branch}`;
52201
+ const originHead = `origin/${branch}`;
52202
+ await withLedgerStep(options.ledger, "branch", async () => {
52203
+ if (await worktreeRegistered(baseRepo, worktreeDir)) {
52204
+ await removeWorktree(baseRepo, worktreeDir);
52205
+ }
52206
+ if (await localBranchExists(baseRepo, branch)) {
52207
+ await runGit(["branch", "-D", branch], baseRepo);
52208
+ }
52209
+ await runGit(["worktree", "add", "-b", branch, worktreeDir, originHead], baseRepo);
52210
+ });
52211
+ const baseSha = (await runGit(["merge-base", "HEAD", originDefault], worktreeDir)).trim();
52212
+ return { baseRepo, worktreeDir, branch, readOnly: false, baseSha, remoteUrl: url2 };
52213
+ }
52144
52214
  async function prepareBuildWorktree(options, baseRepo) {
52145
52215
  const branch = branchName(options.buildRequestId, options.changeset);
52146
52216
  const worktreeDir = buildWorktreeDir(options.baseDir, options.buildRequestId, options.changeset.ordinal);
@@ -52351,6 +52421,147 @@ async function openDraftPullRequestInner(options, retryCounter) {
52351
52421
  }
52352
52422
  throw new Error(`github pr create failed ${created.status}: ${redactSecrets(await readErrorText(created)).slice(0, 500)}`);
52353
52423
  }
52424
+ function classifyMergeReadiness(state) {
52425
+ if (state.merged) {
52426
+ return "already_merged";
52427
+ }
52428
+ if (state.draft) {
52429
+ return "draft";
52430
+ }
52431
+ if (state.mergeable === false || state.mergeableState === "dirty") {
52432
+ return "conflicted";
52433
+ }
52434
+ switch (state.mergeableState) {
52435
+ // clean = all required checks passed, no conflicts. unstable = only NON-required checks are
52436
+ // failing/pending (GitHub still permits the merge) — treat as mergeable.
52437
+ case "clean":
52438
+ case "unstable":
52439
+ return "mergeable";
52440
+ case "blocked":
52441
+ return "blocked";
52442
+ case "behind":
52443
+ return "behind";
52444
+ case "unknown":
52445
+ case "":
52446
+ return "unknown";
52447
+ default:
52448
+ return "blocked";
52449
+ }
52450
+ }
52451
+ function toPullRequestState(json2) {
52452
+ const record2 = json2 && typeof json2 === "object" && !Array.isArray(json2) ? json2 : {};
52453
+ const head2 = record2.head && typeof record2.head === "object" ? record2.head : {};
52454
+ const number4 = typeof record2.number === "number" ? record2.number : Number.NaN;
52455
+ const nodeId = typeof record2.node_id === "string" ? record2.node_id : "";
52456
+ if (!Number.isFinite(number4) || !nodeId) {
52457
+ throw new Error("github pr response missing number or node_id");
52458
+ }
52459
+ return {
52460
+ number: number4,
52461
+ nodeId,
52462
+ merged: record2.merged === true,
52463
+ draft: record2.draft === true,
52464
+ mergeable: typeof record2.mergeable === "boolean" ? record2.mergeable : null,
52465
+ mergeableState: typeof record2.mergeable_state === "string" ? record2.mergeable_state : "unknown",
52466
+ headSha: typeof head2.sha === "string" ? head2.sha : "",
52467
+ mergedSha: typeof record2.merge_commit_sha === "string" && record2.merged === true ? record2.merge_commit_sha : null
52468
+ };
52469
+ }
52470
+ async function fetchPullRequestState(options) {
52471
+ const baseUrl = (options.apiBaseUrl ?? GITHUB_API_BASE_URL).replace(/\/+$/u, "");
52472
+ const fetchImpl = options.fetchImpl ?? fetch;
52473
+ const response = await withGithubRetries(
52474
+ "github pr get",
52475
+ async () => fetchImpl(`${baseUrl}/repos/${options.repo.owner}/${options.repo.name}/pulls/${options.prNumber}`, {
52476
+ method: "GET",
52477
+ headers: githubHeaders(options.credential)
52478
+ })
52479
+ );
52480
+ if (response.status !== 200) {
52481
+ throw new Error(`github pr get failed ${response.status}: ${redactSecrets(await readErrorText(response)).slice(0, 500)}`);
52482
+ }
52483
+ return toPullRequestState(await response.json());
52484
+ }
52485
+ async function mergePullRequest(options) {
52486
+ const baseUrl = (options.apiBaseUrl ?? GITHUB_API_BASE_URL).replace(/\/+$/u, "");
52487
+ const fetchImpl = options.fetchImpl ?? fetch;
52488
+ const { owner, name } = options.repo;
52489
+ if (options.state.merged) {
52490
+ return { status: "merged", mergedSha: options.state.mergedSha };
52491
+ }
52492
+ const merge2 = await withGithubRetries(
52493
+ "github pr merge",
52494
+ async () => fetchImpl(`${baseUrl}/repos/${owner}/${name}/pulls/${options.state.number}/merge`, {
52495
+ method: "PUT",
52496
+ headers: githubHeaders(options.credential),
52497
+ // SHA guard: merge ONLY the head we verified — never pass a merge_method (the repo default
52498
+ // wins; a queue owns the strategy). A stale sha → 409, re-checked by the caller.
52499
+ body: JSON.stringify({ sha: options.state.headSha })
52500
+ })
52501
+ );
52502
+ if (merge2.status === 405) {
52503
+ const detail = await readErrorText(merge2);
52504
+ if (/merge queue/i.test(detail)) {
52505
+ await enablePullRequestAutoMerge({ repo: options.repo, nodeId: options.state.nodeId, credential: options.credential, fetchImpl, apiBaseUrl: baseUrl });
52506
+ return { status: "queued" };
52507
+ }
52508
+ throw new Error(`github pr merge failed 405: ${redactSecrets(detail).slice(0, 500)}`);
52509
+ }
52510
+ if (merge2.status !== 200) {
52511
+ throw new Error(`github pr merge failed ${merge2.status}: ${redactSecrets(await readErrorText(merge2)).slice(0, 500)}`);
52512
+ }
52513
+ const confirmed = await fetchPullRequestState({ repo: options.repo, prNumber: options.state.number, credential: options.credential, fetchImpl, apiBaseUrl: baseUrl });
52514
+ if (!confirmed.merged) {
52515
+ throw new Error("github pr merge postcondition failed: PR is not merged after the merge call");
52516
+ }
52517
+ return { status: "merged", mergedSha: confirmed.mergedSha };
52518
+ }
52519
+ async function enablePullRequestAutoMerge(options) {
52520
+ const baseUrl = (options.apiBaseUrl ?? GITHUB_API_BASE_URL).replace(/\/+$/u, "");
52521
+ const fetchImpl = options.fetchImpl ?? fetch;
52522
+ const response = await withGithubRetries(
52523
+ "github enable auto-merge",
52524
+ async () => fetchImpl(`${baseUrl}/graphql`, {
52525
+ method: "POST",
52526
+ headers: githubHeaders(options.credential),
52527
+ body: JSON.stringify({
52528
+ query: "mutation($id:ID!){enablePullRequestAutoMerge(input:{pullRequestId:$id}){clientMutationId}}",
52529
+ variables: { id: options.nodeId }
52530
+ })
52531
+ })
52532
+ );
52533
+ if (response.status !== 200) {
52534
+ throw new Error(`github enable auto-merge failed ${response.status}: ${redactSecrets(await readErrorText(response)).slice(0, 500)}`);
52535
+ }
52536
+ const body = await response.json();
52537
+ if (Array.isArray(body.errors) && body.errors.length > 0) {
52538
+ throw new Error("github enable auto-merge returned errors");
52539
+ }
52540
+ }
52541
+ async function forcePushWithLease(ws, credential) {
52542
+ if (ws.readOnly || ws.branch.length === 0) {
52543
+ throw new Error("force-push requires a writable build worktree on a branch");
52544
+ }
52545
+ const head2 = (await runGit(["rev-parse", "HEAD"], ws.worktreeDir)).trim();
52546
+ await withAskpass(credential, async (netEnv) => {
52547
+ const before = await runGitNetwork("git ls-remote", ["ls-remote", ws.remoteUrl, `refs/heads/${ws.branch}`], netEnv, ws.worktreeDir, FETCH_TIMEOUT_MS);
52548
+ const beforeSha = before.split(/\s+/)[0]?.trim() ?? "";
52549
+ const forceArg = beforeSha ? `--force-with-lease=refs/heads/${ws.branch}:${beforeSha}` : null;
52550
+ await runGitNetwork(
52551
+ "git push --force-with-lease",
52552
+ ["push", ...forceArg ? [forceArg] : [], "--quiet", ws.remoteUrl, `HEAD:refs/heads/${ws.branch}`],
52553
+ netEnv,
52554
+ ws.worktreeDir,
52555
+ PUSH_TIMEOUT_MS
52556
+ );
52557
+ const remote = await runGitNetwork("git ls-remote", ["ls-remote", ws.remoteUrl, `refs/heads/${ws.branch}`], netEnv, ws.worktreeDir, FETCH_TIMEOUT_MS);
52558
+ const remoteSha = remote.split(/\s+/)[0]?.trim() ?? "";
52559
+ if (remoteSha !== head2) {
52560
+ throw new Error(`git force-push postcondition failed: remote ${ws.branch} is ${remoteSha || "absent"}, expected ${head2}`);
52561
+ }
52562
+ });
52563
+ return { commitSha: head2 };
52564
+ }
52354
52565
  async function removeWorktree(baseRepo, worktreeDir) {
52355
52566
  try {
52356
52567
  await runGit(["worktree", "remove", "--force", worktreeDir], baseRepo);
@@ -52978,7 +53189,7 @@ async function detectCapabilities(cliVersion, env, homeDir) {
52978
53189
  claude: { ...claude, account_alias: accountAlias, rate_limited_until: null },
52979
53190
  codex: { ...codex, account_alias: accountAlias, rate_limited_until: null },
52980
53191
  turn_executions: { report_supported: true },
52981
- ...cliVersion ? { forge_build: cliVersion, seat_work: cliVersion } : {},
53192
+ ...cliVersion ? { forge_build: cliVersion, seat_work: cliVersion, forge_merge: cliVersion } : {},
52982
53193
  git,
52983
53194
  gh,
52984
53195
  scheduler: { max_parallel_sessions_estimate: null },
@@ -53134,6 +53345,11 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtim
53134
53345
  ...result.commitSha ? { commit_sha: result.commitSha } : {},
53135
53346
  ...result.evidence && result.evidence.length > 0 ? { evidence: result.evidence } : {},
53136
53347
  ...result.changedPaths && result.changedPaths.length > 0 ? { changed_paths: result.changedPaths } : {},
53348
+ // DER-1352 (T1.8): the merge-turn postcondition. Only sent when the harness GET-verified the PR
53349
+ // merged on GitHub — the driver's completion writer marks the changeset merged + writes the
53350
+ // decisions row ONLY on this. Gated on result.ok so a failed turn can never carry it.
53351
+ ...result.ok && result.merged === true ? { merged: true } : {},
53352
+ ...result.ok && result.merged === true && result.mergedCommitSha ? { merged_commit_sha: result.mergedCommitSha } : {},
53137
53353
  // DER-1331 (T1.9): the step ledger + the model's (already redacted, bounded) transcript.
53138
53354
  // Only the work-orders result route accepts these today (turn-executions is a separate,
53139
53355
  // narrower endpoint — T1.9 scope); its Zod schema strips unrecognized keys on a non-strict
@@ -53447,6 +53663,7 @@ function buildTurnPrompt(input) {
53447
53663
  const isForgeReview = execution?.profile === "forge_review";
53448
53664
  const isSeatWork = execution?.profile === "seat_work";
53449
53665
  const canFileFindings = execution?.allowed_mcp_tools.includes("rost_record_forge_conformance_findings") ?? false;
53666
+ const canResolveFindings = execution?.allowed_mcp_tools.includes("rost_resolve_forge_conformance_finding") ?? false;
53450
53667
  return [
53451
53668
  hasForgeContext ? `Call rost_get_context first to load your full ${cliBrand.name} charter, then call rost_get_build_context with forge.phase_run_id from the claimed context to load the approved plan, open findings, prior phase summaries, and remaining budgets. Next call rost_list_my_skills and load any assigned Skill you need with rost_get_my_skill_file. Then perform the assigned phase work, address every open finding, report a concise phase status, and stop.` : hasAicosTurnContext ? `Call rost_get_context first to load your full ${cliBrand.name} charter, then call rost_aicos_turn_load_context with turn_execution_id from the claimed context. Answer the AICOS user turn using only that loaded context plus seat-authorized tools. Return only the final concise Markdown answer; no hidden reasoning, tool chatter, raw JSON, secrets, local paths, or unauthorized memory.` : hasAicosContext ? `Call rost_get_context first to load your full ${cliBrand.name} charter, answer the AICOS user turn using only seat-authorized context and tools, return only the final answer as concise Markdown with short paragraphs or real lists, and stop.` : isSeatWork ? `Call rost_get_context first to load your full ${cliBrand.name} charter and current tasks, then carry out the scheduled work your charter authorizes \u2014 accept and complete assigned tasks, record signal readings, file friction, create or attach deliverables, and log your work through your seat tools. Every tool call is re-authorized server-side against your permission manifest, so use only the actions your charter grants and escalate anything beyond your autonomous scope. Report a concise status when done and stop.` : `Call rost_get_context first to load your full ${cliBrand.name} charter, report one read-only status, and stop.`,
53452
53669
  // Forge workspace guidance (phase-specific). The harness owns all git network ops; the
@@ -53456,6 +53673,9 @@ function buildTurnPrompt(input) {
53456
53673
  // T1.6: the review/QA seats record findings through their manifest-granted writer. Only
53457
53674
  // emitted when the seat actually holds the tool (so the builder is never told to file findings).
53458
53675
  canFileFindings ? "When you find a plan-conformance, security, or QA gap, record it with rost_record_forge_conformance_findings \u2014 set changeset_id when the finding targets a specific changeset. A finding is reviewer evidence, not a decision (accepting a gap is a separate human gate). If rost_get_build_context returned a `review` block, review ONLY changesets where review.changesets[].needs_review is true; never re-review an unchanged passing changeset or re-file a prior finding listed in review.prior_findings \u2014 verify those are resolved instead." : "",
53676
+ // DER-1355: on a re-review the seat must RESOLVE a prior finding it verified as fixed —
53677
+ // otherwise the still-open finding routes the request back forever and the loop never converges.
53678
+ canResolveFindings ? "For each finding in your build context's open_findings on a changeset you re-review, verify it against the current diff: if the diff now fixes it, RESOLVE it with rost_resolve_forge_conformance_finding (this is reviewer verification, not human gap-acceptance); if it is still not fixed, leave it open. A resolved finding stops routing the request back to implementation, so the build can converge instead of thrashing to a budget block." : "",
53459
53679
  input.hasWorkspace ? "Load your assigned Skills with rost_list_my_skills and rost_get_my_skill_file before starting; if none are assigned, proceed with your charter and the loaded build context." : "",
53460
53680
  isSeatWork ? "If seat skill tools are available, load your assigned skills before starting; if none are exposed, proceed with your charter and assigned tasks." : "",
53461
53681
  hasAicosTurnContext ? "For AICOS turns, the governed load tool returns the authoritative tenant clock, route context, transcript window, session summary, source counts, read models, and output contract. Never substitute local runtime time or model priors for that loaded tenant clock." : "",
@@ -53473,6 +53693,9 @@ function buildTurnPrompt(input) {
53473
53693
  }
53474
53694
  async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
53475
53695
  const execution = parseRunnerExecutionContract(workOrder);
53696
+ if (execution?.profile === "forge_merge") {
53697
+ return runForgeMergeTurn(ctx, workOrder, execution, runtime);
53698
+ }
53476
53699
  let forgePrep = null;
53477
53700
  if (execution?.needs_repo) {
53478
53701
  const prepared = await prepareForgeTurn(ctx, workOrder, execution);
@@ -53555,6 +53778,271 @@ async function spawnRunnerTurn(ctx, workOrder, runtime, kind) {
53555
53778
  }
53556
53779
  return result;
53557
53780
  }
53781
+ var MERGE_MERGEABILITY_POLLS = 5;
53782
+ var MERGE_MERGEABILITY_POLL_MS = 2e3;
53783
+ var MERGE_QUEUE_POLLS = 10;
53784
+ var MERGE_QUEUE_POLL_MS = 3e3;
53785
+ var MERGE_CONFLICT_SUMMARY_MAX = 12e3;
53786
+ function mergeParkResult(note, extra) {
53787
+ return { ok: true, summary: `[forge-merge] ${note}`.slice(0, MERGE_CONFLICT_SUMMARY_MAX), ...extra };
53788
+ }
53789
+ function mergeMergedResult(mergedSha, note) {
53790
+ return {
53791
+ ok: true,
53792
+ merged: true,
53793
+ ...mergedSha ? { mergedCommitSha: mergedSha } : {},
53794
+ summary: `[forge-merge] ${note}${mergedSha ? ` (${mergedSha})` : ""}`.slice(0, MERGE_CONFLICT_SUMMARY_MAX)
53795
+ };
53796
+ }
53797
+ async function brokerForgeMergeCredential(ctx, workOrder) {
53798
+ const workOrderId = typeof workOrder.id === "string" ? workOrder.id : "";
53799
+ const forge = typeof workOrder.forge === "object" && workOrder.forge !== null ? workOrder.forge : null;
53800
+ const buildRequestId = typeof forge?.build_request_id === "string" ? forge.build_request_id : "";
53801
+ if (!workOrderId || !buildRequestId) {
53802
+ return { ok: false, summary: "[forge-merge] work order missing id or build request id" };
53803
+ }
53804
+ const credentialMintStartedAt = (/* @__PURE__ */ new Date()).toISOString();
53805
+ const brokered = await post2(
53806
+ ctx.fetchImpl,
53807
+ ctx.appUrl,
53808
+ `/api/runner/work-orders/${encodeURIComponent(workOrderId)}/github-credential`,
53809
+ {},
53810
+ ctx.state.runner_secret
53811
+ );
53812
+ ctx.ledger.record({
53813
+ step: "credential_mint",
53814
+ started_at: credentialMintStartedAt,
53815
+ ended_at: (/* @__PURE__ */ new Date()).toISOString(),
53816
+ ok: brokered.status === 200,
53817
+ retry_count: 0,
53818
+ ...brokered.status === 200 ? {} : { error_class: classifyHttpStatus(brokered.status) }
53819
+ });
53820
+ if (brokered.status !== 200) {
53821
+ return { ok: false, summary: `[forge-merge] credential broker failed ${brokered.status}: ${redactForLog(JSON.stringify(brokered.json))}` };
53822
+ }
53823
+ const repoRaw = typeof brokered.json.repo === "object" && brokered.json.repo !== null ? brokered.json.repo : null;
53824
+ const token = typeof brokered.json.token === "string" ? brokered.json.token : "";
53825
+ const expiresAt = typeof brokered.json.expires_at === "string" ? brokered.json.expires_at : "";
53826
+ const owner = typeof repoRaw?.owner === "string" ? repoRaw.owner : "";
53827
+ const name = typeof repoRaw?.name === "string" ? repoRaw.name : "";
53828
+ const defaultBranch = typeof repoRaw?.default_branch === "string" ? repoRaw.default_branch : "";
53829
+ if (!token || !expiresAt || !owner || !name || !defaultBranch) {
53830
+ return { ok: false, summary: "[forge-merge] credential broker returned an incomplete credential" };
53831
+ }
53832
+ return { ok: true, credential: { token, expiresAt }, repo: { owner, name, default_branch: defaultBranch }, buildRequestId };
53833
+ }
53834
+ async function fetchSettledPullRequestState(ctx, input) {
53835
+ let state = await fetchPullRequestState({ repo: input.repo, prNumber: input.prNumber, credential: input.credential, fetchImpl: ctx.fetchImpl });
53836
+ for (let attempt = 0; attempt < MERGE_MERGEABILITY_POLLS && state.mergeable === null && !state.merged; attempt += 1) {
53837
+ await sleep3(MERGE_MERGEABILITY_POLL_MS);
53838
+ state = await fetchPullRequestState({ repo: input.repo, prNumber: input.prNumber, credential: input.credential, fetchImpl: ctx.fetchImpl });
53839
+ }
53840
+ return state;
53841
+ }
53842
+ async function pollForQueueMerge(ctx, input) {
53843
+ for (let attempt = 0; attempt < MERGE_QUEUE_POLLS; attempt += 1) {
53844
+ await sleep3(MERGE_QUEUE_POLL_MS);
53845
+ const state = await fetchPullRequestState({ repo: input.repo, prNumber: input.prNumber, credential: input.credential, fetchImpl: ctx.fetchImpl });
53846
+ if (state.merged) {
53847
+ return { mergedSha: state.mergedSha };
53848
+ }
53849
+ }
53850
+ return null;
53851
+ }
53852
+ async function resolveMergeConflictTurn(ctx, input) {
53853
+ const tenantId = typeof input.workOrder.tenant_id === "string" ? input.workOrder.tenant_id : ctx.state.tenant_id ?? "runner";
53854
+ let workspace;
53855
+ try {
53856
+ workspace = await prepareMergeWorkspace({
53857
+ baseDir: path5.join(ctx.config.homeDir, ".rost", "forge", ctx.state.runner_id),
53858
+ tenantId,
53859
+ buildRequestId: input.buildRequestId,
53860
+ repo: input.repo,
53861
+ credential: input.credential,
53862
+ headBranch: input.headBranch,
53863
+ ledger: ctx.ledger
53864
+ });
53865
+ } catch (error51) {
53866
+ return { pushed: false, result: { ok: false, summary: `[forge-merge] conflict workspace preparation failed: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}`.slice(0, MERGE_CONFLICT_SUMMARY_MAX) } };
53867
+ }
53868
+ const sandbox = resolveSandboxSpec({
53869
+ mode: ctx.config.sandboxMode,
53870
+ platform: process.platform,
53871
+ workspaceDir: workspace.worktreeDir,
53872
+ tmpDir: tmpdir2(),
53873
+ homeDir: ctx.config.homeDir,
53874
+ stateFile: ctx.config.stateFile,
53875
+ runnerBaseDir: path5.join(ctx.config.homeDir, ".rost"),
53876
+ allowWritePaths: [workspace.baseRepo],
53877
+ denyWritePaths: [
53878
+ `${path5.join(workspace.baseRepo, ".git", "hooks")}/`,
53879
+ path5.join(workspace.baseRepo, ".git", "config"),
53880
+ path5.join(workspace.worktreeDir, ".git")
53881
+ ],
53882
+ allowNetwork: ctx.config.sandboxAllowNetwork
53883
+ });
53884
+ const modelResult = await spawnMergeConflictModel(ctx, {
53885
+ workOrder: input.workOrder,
53886
+ execution: input.execution,
53887
+ runtime: input.runtime,
53888
+ workspace,
53889
+ sandbox,
53890
+ defaultBranch: input.repo.default_branch
53891
+ });
53892
+ if (!modelResult.ok) {
53893
+ await removeWorkspace(workspace).catch(() => void 0);
53894
+ return { pushed: false, result: mergeParkResult(`could not resolve the merge conflict (${modelResult.summary.slice(0, 400)}); parking for a human.`) };
53895
+ }
53896
+ try {
53897
+ const pushed = await forcePushWithLease(workspace, input.credential);
53898
+ await removeWorkspace(workspace).catch(() => void 0);
53899
+ return { pushed: true, result: mergeParkResult(`resolved the conflict and force-pushed ${pushed.commitSha}`, { commitSha: pushed.commitSha }) };
53900
+ } catch (error51) {
53901
+ await removeWorkspace(workspace).catch(() => void 0);
53902
+ return { pushed: false, result: { ok: false, summary: `[forge-merge] conflict resolution force-push failed: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}`.slice(0, MERGE_CONFLICT_SUMMARY_MAX) } };
53903
+ }
53904
+ }
53905
+ async function spawnMergeConflictModel(ctx, input) {
53906
+ const mcp = typeof input.workOrder.mcp === "object" && input.workOrder.mcp !== null ? input.workOrder.mcp : {};
53907
+ const token = typeof mcp.token === "string" ? mcp.token : "";
53908
+ const url2 = typeof mcp.url === "string" ? `${ctx.appUrl.replace(/\/+$/, "")}${mcp.url}` : `${ctx.appUrl.replace(/\/+$/, "")}/mcp`;
53909
+ const mcpConfig = JSON.stringify({ mcpServers: { rost: { type: "http", url: url2, headers: { Authorization: `Bearer ${token}` } } } });
53910
+ const configPath = path5.join(tmpdir2(), `rost-runner-mcp-${createHash2("sha256").update(`${url2}:${Date.now()}:${Math.random()}`).digest("hex").slice(0, 16)}.json`);
53911
+ await writeFile3(configPath, `${mcpConfig}
53912
+ `, { mode: 384 });
53913
+ await chmod2(configPath, 384);
53914
+ const plan = resolveTurnPlan({ kind: "work_order", execution: input.execution, repoDir: input.workspace.worktreeDir });
53915
+ const prompt = [
53916
+ "You are resolving a merge conflict on a pull request branch so it can merge into the base branch.",
53917
+ `A WRITABLE checkout of the PR's head branch is at your current working directory. Run \`git merge origin/${input.defaultBranch}\` to merge the base branch in, resolve EVERY conflict so the result is correct and builds, then \`git add\` the resolved files and \`git commit\` the merge. If the project has a fast build or test, run it to confirm the resolution.`,
53918
+ "Do NOT run git push, git remote, or any network git command, and never request or handle GitHub tokens \u2014 the harness pushes and merges after you finish. If the conflict cannot be resolved safely, do NOT force a bad merge: leave the branch uncommitted and stop with a short explanation.",
53919
+ "End with a single concise final message summarizing exactly what you resolved."
53920
+ ].join("\n");
53921
+ let sandboxProfilePath = null;
53922
+ if (input.sandbox.kind === "seatbelt") {
53923
+ sandboxProfilePath = path5.join(tmpdir2(), `rost-runner-sandbox-${createHash2("sha256").update(`${configPath}:${Math.random()}`).digest("hex").slice(0, 16)}.sb`);
53924
+ await writeFile3(sandboxProfilePath, input.sandbox.profile, { mode: 384 });
53925
+ await chmod2(sandboxProfilePath, 384);
53926
+ }
53927
+ const built = buildTurnCommand({ runtime: input.runtime, prompt, configPath, plan });
53928
+ const { command, args } = wrapCommandWithSandbox(input.sandbox, built.command, built.args, sandboxProfilePath);
53929
+ const spawnStartedAt = (/* @__PURE__ */ new Date()).toISOString();
53930
+ const result = await runModelProcess({
53931
+ command,
53932
+ args,
53933
+ cwd: input.workspace.worktreeDir,
53934
+ timeoutMs: plan.timeoutMs,
53935
+ // The model gets the EXPLICIT allowlist env — never the merge token, GIT_ASKPASS, or the bearer.
53936
+ env: buildModelSpawnEnv(process.env),
53937
+ expectSessionEnvelope: false
53938
+ });
53939
+ const exitClass = classifyModelExit(result);
53940
+ ctx.ledger.record({
53941
+ step: "model_exit",
53942
+ started_at: spawnStartedAt,
53943
+ ended_at: (/* @__PURE__ */ new Date()).toISOString(),
53944
+ ok: result.ok,
53945
+ retry_count: 0,
53946
+ ...exitClass.errorClass ? { error_class: exitClass.errorClass } : {}
53947
+ });
53948
+ void rm4(configPath, { force: true });
53949
+ if (sandboxProfilePath) {
53950
+ void rm4(sandboxProfilePath, { force: true });
53951
+ }
53952
+ return result;
53953
+ }
53954
+ async function runForgeMergeTurn(ctx, workOrder, execution, runtime) {
53955
+ const merge2 = execution.merge;
53956
+ if (!merge2) {
53957
+ return mergeParkResult("no merge target bound on the execution contract; parking for a human.");
53958
+ }
53959
+ const prepared = await brokerForgeMergeCredential(ctx, workOrder);
53960
+ if (!prepared.ok) {
53961
+ return { ok: false, summary: prepared.summary };
53962
+ }
53963
+ const { credential, repo, buildRequestId } = prepared;
53964
+ const mergeStartedAt = (/* @__PURE__ */ new Date()).toISOString();
53965
+ try {
53966
+ const outcome = await executeForgeMerge(ctx, {
53967
+ workOrder,
53968
+ execution,
53969
+ runtime,
53970
+ repo,
53971
+ credential,
53972
+ buildRequestId,
53973
+ prNumber: merge2.pr_number,
53974
+ headBranch: merge2.head_branch
53975
+ });
53976
+ ctx.ledger.record({
53977
+ step: "merge",
53978
+ started_at: mergeStartedAt,
53979
+ ended_at: (/* @__PURE__ */ new Date()).toISOString(),
53980
+ ok: outcome.ok,
53981
+ retry_count: 0,
53982
+ ...outcome.ok ? {} : { error_class: "conflict" }
53983
+ });
53984
+ return outcome;
53985
+ } catch (error51) {
53986
+ ctx.ledger.record({
53987
+ step: "merge",
53988
+ started_at: mergeStartedAt,
53989
+ ended_at: (/* @__PURE__ */ new Date()).toISOString(),
53990
+ ok: false,
53991
+ retry_count: 0,
53992
+ error_class: "server_error"
53993
+ });
53994
+ return { ok: false, summary: `[forge-merge] merge turn failed: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}`.slice(0, MERGE_CONFLICT_SUMMARY_MAX) };
53995
+ }
53996
+ }
53997
+ async function executeForgeMerge(ctx, input) {
53998
+ const { repo, credential, prNumber } = input;
53999
+ let state = await fetchSettledPullRequestState(ctx, { repo, prNumber, credential });
54000
+ let readiness = classifyMergeReadiness(state);
54001
+ if (readiness === "already_merged") {
54002
+ return mergeMergedResult(state.mergedSha, "PR already merged on GitHub");
54003
+ }
54004
+ const reviewedHeadSha = input.execution.merge?.head_sha;
54005
+ if (!reviewedHeadSha) {
54006
+ return mergeParkResult("no reviewed head SHA bound on the merge contract; parking for a human (cannot verify the PR head was risk-assessed).");
54007
+ }
54008
+ if (state.headSha !== reviewedHeadSha) {
54009
+ return mergeParkResult(`PR head advanced since review (reviewed ${reviewedHeadSha.slice(0, 12)}, GitHub now ${state.headSha.slice(0, 12) || "unknown"}); the new commits need re-review/re-risk \u2014 parking.`);
54010
+ }
54011
+ if (readiness === "blocked" || readiness === "draft" || readiness === "unknown") {
54012
+ return mergeParkResult(`PR is not ready to merge (state=${state.mergeableState}); parking for a human.`);
54013
+ }
54014
+ if (readiness === "conflicted" || readiness === "behind") {
54015
+ const resolved = await resolveMergeConflictTurn(ctx, {
54016
+ workOrder: input.workOrder,
54017
+ execution: input.execution,
54018
+ runtime: input.runtime,
54019
+ repo: input.repo,
54020
+ credential: input.credential,
54021
+ buildRequestId: input.buildRequestId,
54022
+ headBranch: input.headBranch
54023
+ });
54024
+ if (!resolved.pushed) {
54025
+ return resolved.result ?? mergeParkResult("could not resolve the merge conflict; parking for a human.");
54026
+ }
54027
+ state = await fetchSettledPullRequestState(ctx, { repo, prNumber, credential });
54028
+ readiness = classifyMergeReadiness(state);
54029
+ if (readiness === "already_merged") {
54030
+ return mergeMergedResult(state.mergedSha, "PR merged during conflict resolution");
54031
+ }
54032
+ if (readiness !== "mergeable") {
54033
+ return mergeParkResult(`conflict resolved and pushed, but the PR is not yet mergeable (state=${state.mergeableState}); parking for a re-check.`, resolved.result?.commitSha ? { commitSha: resolved.result.commitSha } : void 0);
54034
+ }
54035
+ }
54036
+ const outcome = await mergePullRequest({ repo, state, credential, fetchImpl: ctx.fetchImpl });
54037
+ if (outcome.status === "merged") {
54038
+ return mergeMergedResult(outcome.mergedSha, "PR merged");
54039
+ }
54040
+ const merged = await pollForQueueMerge(ctx, { repo, prNumber, credential });
54041
+ if (merged) {
54042
+ return mergeMergedResult(merged.mergedSha, "PR merged via the merge queue");
54043
+ }
54044
+ return mergeParkResult("auto-merge enabled (merge queue); the merge was not confirmed within the turn \u2014 parking for a re-check.");
54045
+ }
53558
54046
  function scrubTranscriptSessionIds(value) {
53559
54047
  return value.replace(/("?(?:session_id|sessionId)"?\s*[:=]\s*"?)[A-Za-z0-9._-]+("?)/gi, "$1[redacted]$2");
53560
54048
  }