| name | deliver-pr |
| description | Deliver GitHub pull requests or GitLab merge requests end to end through the released nils-cli `forge-cli pr deliver` macro.
|
Deliver PR / MR
Contract
Prereqs:
agent-runtime, forge-cli >=1.22.12, plan-issue >=1.1.0, and
review-specialists are installed from the released nils-cli package and
available on PATH. The generic code-review outcome uses
review-specialists in pre-merge mode; native review summaries and observed
convergence need forge-cli 1.21.34, guarded pending-review recovery needs
1.22.12, the review-thread merge gate needs 1.0.16, the task-list merge gate
needs 1.0.17, and
existing-PR adoption in pr deliver needs 1.1.0. Linked issue closeout
relies on the unified terminal task-row contract in plan-issue 1.1.0.
- Shared provider, branch, body, and label rules in
references/pr-lifecycle.md are satisfied.
- The working tree contains only the intended delivery changes.
- Local validation and review findings have been resolved before merge.
- Implementation changes have been committed through
semantic-commit; commit
mutation is an internal delivery prerequisite, not a user-selected workflow.
- For every lifecycle mode except close-unmerged, if executable
.agents/scripts/pre-pr.sh exists, run it through the repository dispatcher
before the first provider mutation. Abandon-close is a remote terminal route
and must not depend on unrelated local pre-PR validation.
Inputs:
- Provider:
github or gitlab (let forge-cli detect it from the remote, or
pass --provider explicitly).
- Delivery kind:
feature, bug, chore, docs, ci, or refactor;
it must match the branch prefix.
- PR/MR title and body section files for
agent-runtime pr-body render.
- Optional head branch, base branch, merge method, reviewers, and timeout.
- Requested lifecycle outcome: create only, deliver to readiness, repair review
findings, merge, or close an unmerged record.
- Required labels selected from the shared taxonomy.
- Optional
--no-merge when the workflow should stop after checks.
- Optional
--no-closeout to stop after delivery readiness checks and before
linked issue closeout.
- Mandatory generic code review in pre-merge mode.
- Local terminal identity captured before merge: checkout root, branch,
delivered head SHA, base ref, and whether the checkout is primary or a
managed linked worktree. When an outer L2/L3 or requested post-merge workflow
still owns terminal duties, pass this identity outward instead of cleaning
early.
- If the body references a linked tracking or dispatch issue, use non-closing
references such as
Refs #<issue>; provider auto-close keywords are refused.
Carry the references through pr-body render --issues-file โ rendered as
## Issues after ## Summary for every kind (bug keeps its required
## Issues Found section) โ instead of hand-placing them in the summary.
- If the body references a linked tracking or dispatch issue, lifecycle
readiness is also a pre-merge gate: source, plan, complete state, latest
role=session, validation, and review evidence must be present before merge.
Outputs:
- A draft or ready GitHub PR or GitLab MR opened from the current branch.
- Required checks / pipeline state waited through
forge-cli pr wait-checks.
- A generic pre-merge review result completed before merge with at least
testing and maintainability.
- Compact specialist reviews posted to the PR/MR as each reviewer lens returns
(native
COMMENT review events on GitHub via --submit-review, outcome notes
on GitLab). These use portable --decision comments-only and --lens
semantics and report findings and evidence only; the active environment owns
any optional identity mapping. On GitHub, actionable findings that require owner
changes are also passed through --thread-file so the owning agent can fix and
resolve them; no-finding reports omit --thread-file and stay summary-only.
If a linked tracking or dispatch issue is present, mirror the compact review
URL breadcrumb to that issue.
- A delivery review outcome posted to the PR/MR before merge through
forge-cli pr review; combined owner outcomes use the final --decision
plus repeated selected lenses and own final finding dispositions. GitHub uses
a native APPROVE / REQUEST_CHANGES review only when an environment-owned
router guarantees an identity independent from the PR author; other paths use
the outcome-note form.
- On GitHub, current-head native review summaries inspected through
forge-cli pr reviews and semantically dispositioned before the final owner
outcome. Stale-head reviews remain informational. GitLab retains its outcome
note flow because native review snapshots are GitHub-only in v1.
- Mechanical convergence, unresolved-thread, unchecked-task, and provider-head
gates executed by
forge-cli pr merge. A typed gate failure routes to the
matching read/disposition/retry path instead of an agent-authored polling
loop.
- A merged PR/MR through
forge-cli pr merge, unless --no-merge is supplied.
- When a linked issue closeout runs,
plan-issue record close posts closeout
evidence, repairs the dashboard, verifies linked records, and closes the
issue.
- When this is the outermost successful workflow, a terminal local cleanup
result: the clean primary checkout is restored to base, or the safe merged
managed worktree is removed through
git-cli worktree remove; retained
unsafe state includes its reason and recovery command.
Failure modes:
- Provider auth fails, the branch has no pushed upstream, or the base branch is
not the intended target.
- Required checks / pipeline checks fail, time out, remain pending, or are
missing without an explicit no-checks decision.
- Selected labels fail catalog validation or the provider rejects label
application.
- Mandatory pre-merge review gate findings are unresolved or undispositioned.
- Current-head native review summaries are unread or contain actionable
feedback that has not been repaired, accepted with rationale, or moved to a
follow-up.
- A GitHub native review submission returns
github_pending_review_exists,
but data.pending_reviews[] does not
identify exactly one abandoned current-viewer review for this PR. Stop rather
than deleting ambiguous review state or falling back to an outcome note.
forge-cli pr merge returns review_changes_requested,
review_convergence_activity_changed, review_convergence_head_changed,
review_convergence_timeout, review_snapshot_incomplete,
unresolved_review_threads, or unchecked_task_items. Read and disposition
the matching provider evidence before retrying; do not replace the CLI gate
with a custom timing loop.
- Unchecked
- [ ] task-list items remain in the PR/MR description at merge
time. The description is the delivery contract; forge-cli pr merge fails
closed with unchecked_task_items, and the task-list sweep is how the
workflow dispositions them before that gate trips.
- Delivery review outcome posting fails.
local_path_present: rewrite useful evidence paths in provider-visible PR
bodies, delivery outcome comments, or linked issue closeout records to
$HOME/... and omit remote-useless local artifact paths before retrying.
- A PR/MR body uses a provider auto-close keyword against a linked
plan-tracking or dispatch issue.
- A linked tracking or dispatch issue is missing lifecycle readiness before
merge. Route to
deliver-plan-tracking-issue or deliver-dispatch-plan
instead of merging and backfilling after the fact.
plan-issue record close rejects linked issue closeout.
- Terminal cleanup cannot prove a clean checkout, provider-confirmed merge of
the captured delivered head, or safe ownership. Retain the worktree and
branch, report the failed proof, and do not force removal.
Lifecycle Mode Selection
The user requests the PR/MR outcome, not a lifecycle helper.
- Create only โ render and validate the body, create the draft provider
record with
forge-cli pr create, return its URL, and stop before checks,
review, merge, or linked-issue closeout.
- Deliver โ create or adopt the record, wait for checks, run the mandatory
review gate, inspect and disposition native summaries, then merge through the
CLI-owned convergence/thread/task gates unless the user requested a readiness
stop.
- Review repair โ adopt the existing record, classify unresolved review
threads, make authorized fixes, rerun validation and affected review modes,
and return to the delivery gates.
- Merge โ adopt an existing ready record, inspect native review evidence,
and satisfy every remaining semantic, linked-lifecycle, and provider gate
before
forge-cli pr merge.
- Close unmerged โ only when the user explicitly abandons the record; read
current state, record the reason, and call
forge-cli pr close without
pretending delivery succeeded.
Dispatch lane PR creation remains an internal L3 dispatch role because its
plan-branch target and lane checkpoint authority belong to that outcome.
Body Format
Use agent-runtime pr-body render as the canonical formatter. The shared
PR/MR lifecycle reference owns minimum headings, label selection, and
non-closing issue references.
Entrypoint
Render the body with agent-runtime before calling the delivery macro:
agent-runtime pr-body render \
--kind feature \
--summary-file "$SUMMARY_FILE" \
--changes-file "$CHANGES_FILE" \
--test-first-file "$TEST_FIRST_FILE" \
--test-plan-file "$TEST_PLAN_FILE" \
--risk-file "$RISK_FILE" \
--out "$PR_BODY"
Add --issues-file "$ISSUES_FILE" when the PR references a linked issue: it is
required for --kind bug and optional for every other kind, rendering the
non-closing references as ## Issues. Kind-specific files passed with a
non-owning kind are rejected (--changes-file is feature-only;
--problem-file, --reproduction-file, and --fix-approach-file are
bug-only) instead of being silently dropped.
Use the released provider CLI directly. forge-cli detects the provider from
the remote; pass --provider "$PROVIDER" to pin it (github or gitlab):
forge-cli pr deliver \
--provider "$PROVIDER" \
--kind feature \
--title "$PR_TITLE" \
--body-file "$PR_BODY" \
--base main \
--method squash \
--label type::feature \
--label area::runtime \
--label size::m \
--label-catalog manifests/forge-labels.yaml \
--strict-labels \
--test-first-evidence "$EVIDENCE_DIR" \
--no-merge
When the test-first gate is enabled โ [test_first].require = true in a repo
.forge-cli.toml or the user-global
${XDG_CONFIG_HOME:-~/.config}/forge-cli/config.toml โ a --kind feature /
bug deliver (the create, adopt, and --dry-run preflight steps) also requires
--test-first-evidence "$EVIDENCE_DIR", pointing at the verify-clean directory
the policy-owned test-first-evidence CLI flow produces. Omit it for the exempt kinds (docs /
chore / ci / refactor); without it delivery fails closed with
test_first_evidence_required.
Run the generic code-review outcome in pre-merge mode before merge. Its minimum
underlying scope is:
review-specialists scope \
--base "$BASE_REF" \
--testing \
--maintainability \
--format json
PRE_SUBMIT_PR="$(
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" \
--format json pr view "$PR_NUMBER"
)"
EXPECTED_REVIEW_HEAD="$(
printf '%s\n' "$PRE_SUBMIT_PR" |
jq -er 'select(.ok == true) | .data.head_sha'
)" || exit $?
readonly EXPECTED_REVIEW_HEAD
if [ "$PROVIDER" = github ]; then
PRE_SUBMIT_REVIEWS="$(
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" \
--format json pr reviews "$PR_NUMBER"
)"
printf '%s\n' "$PRE_SUBMIT_REVIEWS"
printf '%s\n' "$PRE_SUBMIT_REVIEWS" |
jq -e --arg head "$EXPECTED_REVIEW_HEAD" \
'.ok == true and .data.head_sha == $head' >/dev/null
fi
SELECTED_REVIEW_LENSES=(testing maintainability)
REVIEW_LENS_ARGS=()
for selected_lens in "${SELECTED_REVIEW_LENSES[@]}"; do
REVIEW_LENS_ARGS+=(--lens "$selected_lens")
done
FINAL_SUBMIT_REVIEW=()
case "${AGENT_RUNTIME_FORGE_IDENTITY_ROUTER_REQUIRED:-}" in
1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss])
[ "$PROVIDER" = github ] &&
FINAL_SUBMIT_REVIEW=(--submit-review --expected-head "$EXPECTED_REVIEW_HEAD")
;;
esac
REVIEW_CONVERGENCE_ARGS=()
[ "$PROVIDER" = gitlab ] && REVIEW_CONVERGENCE_ARGS=(--review-convergence=false)
EXPECTED_REVIEW_BODY="$(cat "$DELIVERY_REVIEW_OUTCOME")" || exit $?
readonly EXPECTED_REVIEW_BODY
NATIVE_REVIEW_CMD=(
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" --format json
pr review "$PR_NUMBER"
--decision "$REVIEW_DECISION"
"${FINAL_SUBMIT_REVIEW[@]}"
--comment="$EXPECTED_REVIEW_BODY"
"${REVIEW_LENS_ARGS[@]}"
)
unset PENDING_REVIEW_ID
set +e
NATIVE_REVIEW_JSON="$("${NATIVE_REVIEW_CMD[@]}" 2>&1)"
NATIVE_REVIEW_STATUS=$?
set -e
if [ "$NATIVE_REVIEW_STATUS" -ne 0 ]; then
if [ "$PROVIDER" != github ] || ! printf '%s\n' "$NATIVE_REVIEW_JSON" |
jq -e '.ok == false and .error.code == "github_pending_review_exists"' \
>/dev/null; then
printf '%s\n' "$NATIVE_REVIEW_JSON" >&2
exit "$NATIVE_REVIEW_STATUS"
fi
if [ "$PROVIDER" = github ]; then
POST_CONFLICT_REVIEWS="$(
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" \
--format json pr reviews "$PR_NUMBER"
)"
PENDING_REVIEW_ID="$(
printf '%s\n' "$POST_CONFLICT_REVIEWS" |
jq -er --arg head "$EXPECTED_REVIEW_HEAD" \
--arg body "$EXPECTED_REVIEW_BODY" '
select(.ok == true and .data.head_sha == $head)
| [.data.pending_reviews[]
| select(.state == "PENDING")
| select(.commit_sha == $head)
| select(.summary_truncated == false)
| select((.summary | rtrimstr("\n")) == ($body | rtrimstr("\n")))]
| select(length == 1)
| .[0].id
'
)"
if [ -n "${PENDING_REVIEW_ID:-}" ]; then
DELETE_REVIEW_JSON="$(
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" \
--format json pr pending-review delete "$PR_NUMBER" \
--review "$PENDING_REVIEW_ID" \
--expected-head "$EXPECTED_REVIEW_HEAD" \
--expected-commit "$EXPECTED_REVIEW_HEAD" \
--expected-body="$EXPECTED_REVIEW_BODY" \
--confirm-abandoned
)"
POST_DELETE_REVIEWS="$(
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" \
--format json pr reviews "$PR_NUMBER"
)"
printf '%s\n' "$POST_DELETE_REVIEWS" |
jq -e --arg head "$EXPECTED_REVIEW_HEAD" \
--arg id "$PENDING_REVIEW_ID" '
.ok == true
and .data.head_sha == $head
and (.data.pending_reviews | map(.id) | index($id) | not)
' >/dev/null
set +e
NATIVE_REVIEW_RETRY_JSON="$("${NATIVE_REVIEW_CMD[@]}" 2>&1)"
NATIVE_REVIEW_RETRY_STATUS=$?
set -e
if [ "$NATIVE_REVIEW_RETRY_STATUS" -ne 0 ]; then
printf '%s\n' "$NATIVE_REVIEW_RETRY_JSON" >&2
exit "$NATIVE_REVIEW_RETRY_STATUS"
fi
NATIVE_REVIEW_JSON="$NATIVE_REVIEW_RETRY_JSON"
fi
fi
fi
printf '%s\n' "$NATIVE_REVIEW_JSON"
forge-cli --provider "$PROVIDER" pr merge "$PR_NUMBER" --method squash \
--expected-head "$EXPECTED_REVIEW_HEAD" \
"${REVIEW_CONVERGENCE_ARGS[@]}"
If a GitHub pr review --submit-review call returns
github_pending_review_exists, preserve the failed command status and JSON,
then fetch a fresh post-conflict pr reviews result.
The command binds the native review to the inspected head with
--expected-head. From
data.pending_reviews[], recover only when exactly one current-viewer node is
the abandoned attempt for this PR and the intended body, decision, and head
are still current. Never choose a node from submitted reviews, delete multiple
nodes, or use recovery for an unrelated rejection. The executable state machine
uses only that exact node id.
The delete primitive independently verifies exact PR membership, pending
state, current-viewer authorship, and delete permission. After the refreshed
snapshot confirms the pending node is gone, retry the unchanged failed review
once; if the guard, refresh, retry, or a second rejection fails, stop and
preserve the provider error.
Map the final delivery review outcome to approve when delivery may merge and
request-changes when the review blocks. Use comments-only only for
specialist review comments or other non-decisional notes, not for the final
combined delivery-owner outcome. On GitHub, --submit-review makes this a native
pull request review event (approveโAPPROVE, request-changesโREQUEST_CHANGES)
authored by the adapter-selected independent identity. Without the capability,
GitHub uses the same outcome-note path as GitLab, records the semantic decision,
and does not mutate native approval state.
For identity and issue mirroring: post a compact specialist review comment
after each reviewer lens returns and after each focused follow-up rerun. Pass
only the portable --provider, --decision, and --lens semantics; do not
set private identity-profile environment variables in this public workflow.
The active provider CLI uses ambient identity unless an environment-owned
adapter maps those semantic flags. When the PR/MR is linked to a tracking or
dispatch issue and the issue number is available, add
--issue "$ISSUE" --mirror-issue so the issue activity shows review progress
without duplicating full outcome bodies.
When the specialist report has actionable GitHub findings, include
--thread-file "$REVIEW_THREAD_FILE" on the first specialist review post that
surfaces those findings. Omit --thread-file for clean reviews, informational
notes, follow-up pass summaries, and the final combined approval outcome.
Before the final owner outcome, read native provider reviews once:
if [ "$PROVIDER" = github ]; then
forge-cli --provider "$PROVIDER" --format json pr reviews "$PR_NUMBER"
fi
On GitHub, treat data.current_head_reviews[].summary as evidence, never as a
machine verdict: repair actionable feedback, accept it with rationale, or move
it to a follow-up before posting the final combined owner outcome. Stale-head
reviews are informational. GitLab has no native snapshot in v1 and keeps the
existing specialist/outcome-note flow. If summary_truncated is true, retrieve
the full review body through provider read tooling before disposition; stop if
the full body cannot be read. Do not poll or sleep in agent instructions; the
released forge-cli pr merge owns the configured observed-bot quiet period,
timeout, complete snapshot, final recheck, native CHANGES_REQUESTED,
unresolved-thread, unchecked-task, and provider-head gates.
Observed convergence is GitHub-only in v1. On GitLab, pass
--review-convergence=false explicitly so a user-global GitHub policy does not
turn a supported MR delivery into provider_unsupported.
If merge returns review_convergence_activity_changed, read pr reviews
again, disposition the new current-head evidence, refresh the final owner
outcome, and retry. For review_changes_requested or
review_snapshot_incomplete, inspect pr reviews and stop until the native
state is cleared or complete. For unresolved_review_threads, use
forge-cli pr review-threads list, then repair, reply and resolve as accepted,
or create a follow-up and resolve with its link per
core/policies/review-thread-convergence.md. For unchecked_task_items, use
forge-cli pr tasks, then finish/check the item or rewrite it as deferred with
a follow-up ref. review_convergence_head_changed requires rebinding delivery
evidence to the new head, then re-run validation and affected review lenses,
read current-head summaries again, and post a new owner outcome before retrying.
Timeout failures require a stable provider state before retry. Bypass flags
remain exceptional and their rationale belongs in the delivery review outcome.
For linked tracking or dispatch issues, run a pre-merge lifecycle audit before
the merge. This is not closeout yet, because record close verifies the merged
PR/MR after merge:
forge-cli --provider "$PROVIDER" --repo "$OWNER_REPO" --format json \
issue view "$ISSUE" --with-comments >"$ISSUE_VIEW_JSON"
jq '{body:.data.body, comments:(.data.comments // [])}' \
"$ISSUE_VIEW_JSON" >"$ISSUE_JSON"
jq -r .body "$ISSUE_JSON" >"$ISSUE_BODY"
plan-issue --format json record audit \
--profile "$PROFILE" \
--body-file "$ISSUE_BODY" \
--comments-json "$ISSUE_JSON"
Stop if the audit lacks session evidence, if the latest state is not
complete, or if the dashboard still shows Latest session: pending.
Run linked issue closeout after merge when the body references a tracking or
dispatch issue via Refs #<issue> and --no-closeout was not supplied. Use the
provider-correct linked record ref: $OWNER_REPO#$PR_NUMBER on GitHub,
$OWNER_REPO!$MR_NUMBER on GitLab:
plan-issue --repo "$OWNER_REPO" --format json record close \
--issue "$ISSUE" \
--profile "$PROFILE" \
--linked-pr "$LINKED_RECORD_REF" \
--approval "$APPROVAL" \
--bundle "$PLAN_BUNDLE" \
--add-label state::closed \
--remove-label state::needs-triage
Use profile=tracking for lightweight plan-tracking issues and
profile=dispatch for dispatch plan records.
Workflow
- Confirm the branch, base, dirty-tree scope, validation evidence, review
outcome, and requested lifecycle mode. Ensure implementation commits were
created through
semantic-commit.
- In close-unmerged mode, read the current provider record, record the abandon
reason, run
forge-cli pr close and stop before delivery or local pre-PR
validation.
- If
.agents/scripts/pre-pr.sh is executable, run it through the repository
dispatcher and stop on failure.
- Inspect linked issues and closing references. For issue-backed plan work,
use
Refs #<issue> until record close has passed.
- Render the PR/MR body with
agent-runtime pr-body render.
- Select labels before provider mutation; use
references/pr-lifecycle.md for the shared taxonomy rule.
- If
manifests/forge-labels.yaml exists, validate labels with the
appropriate forge-cli label surface before the first live delivery.
- In create-only mode, run
forge-cli pr create, return the provider URL, and
stop. Otherwise run forge-cli pr deliver with selected --label flags,
--label-catalog manifests/forge-labels.yaml when present, and
--no-merge so checks / pipelines complete before the mandatory review gate.
- Run the generic code-review outcome in pre-merge mode.
- Keep review workers read-only. As each reviewer lens returns,
post one compact specialist review comment through
forge-cli pr review
(a native COMMENT review event via --submit-review on GitHub)
with --decision comments-only and that semantic --lens. The parent
delivery workflow posts; reviewer
subagents never call the provider. Post the moment each lens returns โ before
the repair in step 11, never batched after it; the comment is the finding the
step-11 fix responds to, so it must exist first (see
REVIEW_OUTCOME_POSTING_CONTRACT.md, posting order). On GitHub, attach
--thread-file for actionable findings so the fix can close a native review
thread; summary-only reviews omit it.
- Repair concrete findings in this delivery workflow, then rerun validation,
checks, and affected review lenses. Post each focused follow-up specialist
review comment with the same semantic lens before continuing.
- On GitHub, read
forge-cli pr reviews once after specialist repairs and
semantically disposition every actionable current-head summary; stale-head
reviews are informational. When summary_truncated is true, obtain the full
review body and stop if it is unavailable. On GitLab, retain the outcome-note
path and do not invoke the unsupported snapshot. Do not implement a polling
or sleep loop in the workflow.
- Post the final combined delivery review outcome body produced by the
generic review's pre-merge mode with
forge-cli pr review before merge. Use
the final --decision and repeat every selected --lens; add native GitHub
approval only through the declared independent-identity capability, and keep
identity selection outside the public skill. If native submission
returns github_pending_review_exists, use the exact-node
pending_reviews recovery above and retry the unchanged outcome once; do
not delete ambiguous drafts or downgrade the outcome to a note.
- Before merge, if the PR/MR references a linked tracking or dispatch issue,
audit it and confirm lifecycle readiness: source/plan snapshots, complete
state, latest
role=session, validation, review, and dashboard links are
present. If not, stop and route to the matching plan delivery workflow.
- Merge with
forge-cli --provider "$PROVIDER" pr merge "$PR_NUMBER" unless
--no-merge is the requested final stop. The CLI owns observed quiet
timing, complete/final native-review reads, native change requests,
thread/task gates, and head CAS. On
review_convergence_activity_changed, re-read pr reviews, disposition
the new evidence, refresh the final owner outcome, and retry. Route other
typed review/thread/task failures through the matching read surface and the
same repair/accept/follow-up discipline before retrying.
review_convergence_head_changed additionally requires delivery-evidence
rebinding, validation and affected-review reruns, and a new owner outcome on
the new head before retry.
- After merge, if the body referenced a linked tracking or dispatch issue
and
--no-closeout was not supplied, run plan-issue record close with
the correct profile. On gate fail, leave the issue open with the blocked
code surfaced by plan-issue and route to the matching closeout skill.
- Record the PR/MR URL, labels, check/pipeline evidence, review outcome, merge
commit, chained closeout result, and any fallback used in delivery notes.
- If this workflow is the outermost terminal owner, finish any requested
post-merge deployment, activation, archive, evidence, and local closeout
duties, then apply
core/policies/git-delivery.md terminal cleanup. Recheck
status and provider merge/head truth. Restore a clean primary checkout to
base, or invoke git-cli worktree remove <path-or-slug> --format json from
the primary checkout through the supported hooked shell; the target-aware
lease guard must confirm no live foreign owner before removal. If that proof
or hook is unavailable, retain the worktree. Delete the local
branch only when its tip equals the provider-confirmed delivered head;
otherwise retain and report it. If an outer L2/L3 workflow remains, hand it
the captured identity and defer this step.
Boundary
forge-cli owns provider create, checks/pipeline wait, ready, native-review
convergence, thread/task enforcement, provider-head binding, and merge calls.
plan-issue record owns linked issue lifecycle closeout. The workflow owner
owns scope judgment, code changes, local validation, pre-merge gate decisions,
repair loops, delivery outcome comments, and any temporary provider fallback
decision. The outermost workflow also owns terminal local cleanup after all
downstream duties; child delivery workflows hand off rather than clean early.
Provider auto-close keywords against issue-backed plan records remain banned.