Imported from EdgeApp/edge-dev-agents (
.cursor/skills/pr-land/SKILL.md). Install upstream withnpx skills add EdgeApp/edge-dev-agents --skill pr-land. Copyright stays with the author.
Land approved PRs by autosquashing fixups, rebasing onto the default upstream branch, and merging. Accepts repo names, explicit PR references, or Asana task URLs.
Arguments are classified automatically:
- No args → queries the Engineering Board's "Merge/Finalize" section (matched by name at call time in
pr-land-discover.sh; a missing section errors with the names that exist), filters to incomplete tasks assigned to the current Asana user (resolved fromASANA_TOKENvia~/.cursor/skills/asana-whoami.sh), and walks each task's attachments + subtasks for GitHub PR links. Tasks with no PR link are reported inerrorsbut do not block. Only ONE release lands per run: by default the lowest "Release (4.x.x)" among those tasks (4.51 and 4.52 both queued → only 4.51 lands). Later-release tasks and tasks with no release set come back indeferredTasks, unresolved. - A release (
4.52, or--release 4.52) → the same section scan, limited to that release.--all-releases→ the same section scan with no release filter. Both refuse to combine with explicit args (exit 2); explicit args are never release-filtered. --branch-scan→ legacy behavior: scans all EdgeApp repos for$GIT_BRANCH_PREFIX/*PRs.- Repo names → branch-prefix scan, limited to the named repos.
- PR URLs / shorthand (
repo#N) → fetched directly, no branch-prefix filter. - Asana task URLs → resolved to linked GitHub PRs via Asana API (requires
ASANA_TOKEN). Parent tasks are walked: each subtask's attachments are scanned for PRs; subtasks without a linked PR are skipped silently (e.g. a verification-only subtask).
| Script | Purpose |
|---|---|
pr-land-discover.sh |
Discover PRs and approval status |
pr-land-comments.sh |
Check for recent unaddressed feedback (inline threads, review bodies, top-level comments) + unresolved reviewer-bot threads (botThreads, no recency filter — see bot-thread-gate) |
git-branch-ops.sh |
Shared autosquash / push helper for explicit git branch actions |
pr-land-prepare.sh |
Rebase + conflict detection + verification |
verify-repo.sh |
Verification (CHANGELOG + code; lint scoped to changed files when --base given; accommodates both Unreleased-style and legacy versions-only CHANGELOG formats; prepare invokes it with --require-changelog, so every landed PR must include a CHANGELOG entry) |
pr-land-merge.sh |
Rebase + verify + merge via GitHub API |
pr-land-automerge.sh |
Arm GitHub auto-merge (or turn it off with --disarm), gated on reviewer bots being done and bot threads clear (see bot-thread-gate) |
pr-land-publish.sh |
Version bump, changelog update, commit + tag (no push) |
staging-cherry-pick.sh |
Cherry-pick merged PR commits onto staging (see /staging-cherry-pick skill) |
asana-task-update.sh |
Update linked Asana tasks after merge |
pr-merge-watch.sh |
Babysit armed PRs: poll until all merge, a check fails, one needs a rebase (DIRTY, or BEHIND with green checks — auto-merge never updates a BEHIND branch), or one is green but blocked solely on a missing approving review |
changelog-union-merge.sh |
Mechanically resolve a CHANGELOG rebase/cherry-pick conflict (union, dedupe, type-order). Shared with /develop-staging, which adds whole-section merging behind --release-merge; pr-land never passes that flag |
npm-publish-web.sh |
Login-if-needed + publish via npm web/link auth under a PTY; emits AUTH_URL and AUTH_DONE lines to relay |
wait-for-quiet-load.sh |
Blocks (bounded) until the 1-min load average, minus this session's own process tree, is at or below 2 x CPUs; called by every verification path, exits 0 always |
force-land-rationale.sh |
Post the pre-merge rationale comment an admin merge requires and write the marker its gate reads (see force-land-review-bypass) |
| Script | Exit 0 | Exit 1 | Exit 2 | Exit 3 | Exit 4 |
|---|---|---|---|---|---|
pr-land-discover.sh |
Success | Error | Auth needed | - | - |
pr-land-comments.sh |
Success | Error | - | - | - |
git-branch-ops.sh |
Success | Error | - | - | - |
pr-land-prepare.sh |
Ready | All failed | - | Base branch unbuildable (broken-develop-gate) |
- |
verify-repo.sh |
Pass | Code fail | CHANGELOG fail | - | - |
pr-land-merge.sh |
Merged | Verify fail | - | - | Conflict needs resolution (CHANGELOG or code) |
pr-land-automerge.sh |
All armed/disarmed/merged | Blocked / unsupported / error | Usage or missing dep | - | - |
staging-cherry-pick.sh |
All cherry-picked | Error | Auth needed | CHANGELOG conflict | - |
pr-land-publish.sh |
Ready (needs push) | Verify fail | No unreleased | - | - |
asana-task-update.sh |
Success | Error | Needs user input | - | - |
pr-merge-watch.sh |
All merged | Usage error | - | Needs rebase (re-prepare listed PRs) | Check failed |
changelog-union-merge.sh |
Resolved (+continued) | No markers / continue failed | Usage | - | - |
npm-publish-web.sh |
Published | npm exited without publishing | Auth never completed | Terminal registry rejection | Tarball shrank |
force-land-rationale.sh |
Posted (marker written) | Checks not green / error | Usage, or no Force Land authority | - | - |
(npm-publish-web.sh exit 5 = accepted-not-served: npm accepted the publish, the registry has not served it yet. Re-run to re-check; never re-publish. See npm-publish-auth.)
(pr-land-automerge.sh exit 75 = another session holds the repo land lease, per repo-land-mutex. Exit 76 = at least one PR is only WAITING on a reviewer bot: nothing to fix and nothing armed — let the bots finish, then re-run the same call.)
(pr-merge-watch.sh exit 5 = overall timeout with PRs still pending. Exit 6 = BLOCKED_ON_REVIEW: all checks green, only the approving review missing — handle per force-land-review-bypass. Exit 7 = CONTINUE: the per-call cap hit (each invocation self-bounds under the Bash foreground limit) — re-invoke with the SAME args immediately; the overall --timeout budget persists across calls, so this is one bounded watch, not a fresh one.)
Any exit code not in this table = STOP immediately and report to user.
status |
Meaning | Prescribed action |
|---|---|---|
ready |
Prepared + verified | Proceed to push (step 4). Check placementWarnings first. |
changelog_conflict |
CHANGELOG-only rebase conflict, left in progress | Run changelog-union-merge.sh <repoDir> --continue (mechanical union: dedupe + type-order), re-run prepare. Resolve by hand only if the script exits non-zero. |
code_conflict |
Code-file rebase conflict, rebase LEFT IN PROGRESS | Resolve semantically in place when confidently determinable (dep/version bumps, lockfile regen, accept upstream deletion, non-overlapping edits): keep both sides' intent, regenerate lockfiles if deps changed, git add + GIT_EDITOR=true git rebase --continue, re-run prepare. git rebase --abort + skip ONLY if not confidently resolvable. See code-conflicts. |
verification_failed |
verify-repo.sh failed | Read failedStep + logPath from the JSON; inspect the log tail (tail -40 <logPath>); fix only if trivially in-scope, else report. Special case failedStep: "CHANGELOG entry existence check" — prepare REQUIRES every landed PR to have updated CHANGELOG.md: add a correctly-formatted entry for the PR's change (under ## Unreleased, or the topmost version section in legacy versions-only repos), amend it onto the branch, and re-run prepare. Only if an entry is genuinely unwarranted (e.g. CI-only change), ask the user whether to land without one. |
install_failed |
Dependency install failed | Read the error: a Socket Firewall HTML page ("Please connect to Socket Firewall") is a transient proxy outage — retry the prepare ONCE, then report. Runtime-setup steps in scripts.prepare are already stripped during verification installs (see verification-prepare-cmd.sh), so a persistent failure is a real install problem: report, do not retry further. |
autosquash_failed |
Fixup autosquash rebase failed (aborted) | Report; branch likely needs manual history repair. |
checkout_failed |
Fetch/checkout failed | Report the git error. Note: dirty trees no longer cause this — they are auto-stashed (see dirty-tree-policy). |
clone_failed |
Initial clone failed | Report; check repo name/access. |
Dirty-tree policy (dirty-tree-policy): prepare operates on the PRIMARY checkout at ~/git/<repo> (or a worktree already holding the branch) — NOT a scratch clone — so it can collide with in-progress local work. If the tree is dirty at checkout, prepare auto-stashes it (including untracked) under a labeled stash pr-land-autostash <ISO-date> (was on <branch>) and reports it in the per-PR JSON (autostash) and the summary. ALWAYS surface auto-stashes to the user in your final report — the stash is their uncommitted work; recovery is git stash list | grep pr-land-autostash then git stash pop <ref>.
~/.cursor/skills/pr-land/scripts/pr-land-discover.sh [args...]
Args can be repo names, PR URLs, PR shorthand (repo#N), Asana task URLs (mixed freely), or --branch-scan.
No args = pull incomplete tasks assigned to me from the Engineering Board's "Merge/Finalize" section, keep only the lowest release, and walk each for PR attachments + subtask PR attachments. Use --branch-scan for the legacy "scan all EdgeApp repos for $GIT_BRANCH_PREFIX/* PRs" behavior.
Release selection is the operator's call, never the agent's: pass --release <v> only when the operator names a release ("land 4.52", "/pr-land 4.52 tasks" → --release 4.52, dropping the non-release words), and --all-releases only when they ask for every release. Otherwise run with no args and let the lowest-release default stand. Name the selected release in the landing summary and list every deferredTasks entry (name + release, or "no release set") as not landed.
Returns JSON: { "prs": [...], "errors": [...] }, plus release (mode: lowest/requested/all, selected) and deferredTasks on a section scan. Each PR has repo, prNumber, branch, title, approved, changesRequested, reviewers. Errors include Asana resolution failures or PR fetch failures.
- If BOTH arrays are non-empty → mixed-batch path per
defer-gui: onlynonGuiPrsflow through steps 3-7. Tell the user:Deferring <N> GUI PR(s) until after non-GUI deps are published and upgraded on develop. - If only one array is non-empty → no deferral; all PRs flow through steps 3-7 normally.
Returns PRs with unaddressed feedback posted after the last commit. The script checks three sources and includes the IDs needed to reply or mark them addressed:
- Unresolved inline review threads — threads where
isResolved: falsewith comments newer than last commit - Review bodies — the latest review from each non-author/non-bot reviewer, if it has a non-empty body newer than last commit (catches feedback written in the approve/reject dialog, regardless of review state)
- Top-level PR comments — non-author/non-bot comments newer than last commit
Items previously marked with <!-- addressed:review:ID --> or <!-- addressed:comment:ID --> markers are automatically excluded.
Do NOT block the rest of the flow for PRs with comments.
ONE tool call per batch:
echo '[{"repo":"...","branch":"<prefix>/feature","buildField":"staging"}]' | ~/.cursor/skills/pr-land/scripts/pr-land-prepare.sh
Include buildField per branch with the task's Build field value already resolved during discovery (build-field-routing) — "staging" arms the deterministic CHANGELOG placement check below; omit it for tasks with no Build field.
The prepare script handles: clone/checkout, autosquash fixups, rebase onto upstream (the repo's actual default branch via origin/HEAD; origin/develop for the GUI), conflict detection, and verification. It operates on the PRIMARY checkout at ~/git/<repo> — not a scratch clone — so in-progress local work there is auto-stashed per dirty-tree-policy. Per-PR outcomes are the status values in <prepare-statuses>; act on each as prescribed there.
Exit codes:
0= At least one PR ready to push, OR a resolvable conflict (code/CHANGELOG) was left in progress (reported incodeConflicts/changelogConflicts)1= All PRs failed (verification or other errors, none ready or resolvable)3= Every PR was refused because its base branch is memoized as unbuildable (baseUnbuildable); applybroken-develop-gate
The rebase is LEFT IN PROGRESS (status code_conflict, reported in codeConflicts). Resolve per the code-conflicts rule: if confidently determinable, edit each conflicted file to keep both sides' intent, regenerate lockfiles if deps changed, git add + GIT_EDITOR=true git rebase --continue, then re-run prepare to verify. If NOT confidently resolvable, git rebase --abort and skip, continuing with other PRs.
Run ~/.cursor/skills/pr-land/scripts/changelog-union-merge.sh <repoDir> --continue, then re-run prepare. Only resolve by hand (upstream entries first, then ours) if the script exits non-zero.
reason: "staging-task-under-unreleased" — the task's Build field is staging (operator intent) but the entry sits under ## Unreleased (develop). Deterministic, NO user ask (a yolo run has no one to ask, and the field already IS the decision): use the Edit tool to move the entry line(s) into the ## X.Y.Z (staging) section, preserving added → changed → deprecated → fixed → removed → security ordering, then amend the top commit and re-run prepare exactly as in step 2 of the interactive case below. This is what keeps develop's changelog honest — without the move, the fix ships in the staging release but both branches list it as unreleased, and it double-appears in the NEXT version's notes.
reason: "released-section" — the PR added CHANGELOG entries under a DATED released heading (e.g. ## 4.46.0 (2026-03-20)) instead of ## Unreleased (develop) or ## X.Y.Z (staging). This usually means the author placed the entry under the then-current released version but the PR actually targets a later unreleased version. A judgment call:
Do NOT push (step 4) until the user decides. For each warning, show the user the line, section, and text, then ask exactly:
CHANGELOG entry under released section "<section>":
<text>
(a) leave as-is (b) move to ## Unreleased (develop) (c) move to ## X.Y.Z (staging)
- If user picks (a): continue to step 4.
- If user picks (b) or (c): use the Edit tool to move the offending line(s) into the target section, preserving
added → changed → deprecated → fixed → removed → securityordering within that section. Then stage and amend the top commit on the branch:
Re-rungit -C <repoDir> add CHANGELOG.md && GIT_EDITOR=true git -C <repoDir> commit --amend --no-editpr-land-prepare.shto re-verify before pushing. Do NOT bypass precommit hooks.
~/.cursor/skills/git-branch-ops.sh push --force-with-lease --branch <branch>
-
Local gate (Steps 3-4): run Step 3
pr-land-prepare.sh(autosquash + rebase onto upstream +verify-repo.sh) and Step 4 push FIRST. This catches breakage locally before CI spends time on it. Only branches that reachstatus: readyand are pushed proceed to arm. -
Confirm + arm (after the bots, never before): after Step 1-2 (discovery + comments addressed/approved) and the local gate, confirm with the user, then arm GitHub auto-merge so each PR merges itself when its required CI checks go green (GitHub owns the rebase/queue and the actual merge). Arming waits on the reviewer bots completing on the PUSHED head per
bot-thread-gate:echo '[{"repo":"...","prNumber":123}, ...]' | ~/.cursor/skills/pr-land/scripts/pr-land-automerge.shPer-PR result lines:
armed(auto-merge on; GitHub merges on green),merged(already merged),waiting(a reviewer bot is still running on HEAD — NOT armed),blocked(changes requested, or an unresolved reviewer-bot thread — resolve first),unsupported(repo disallows auto-merge/merge-commit → use the local fallback below),error. Exit 0 = all armed/merged. Exit 76 =waitingonly: let the bots' check-runs finish (gh pr checks <n>, orwatch-pr.shin an orchestrated run), then re-run the SAME call — nothing was armed, so the re-run is the whole remedy. -
Watch until merged or actionable (babysit): run the watcher over every armed PR (background it for long CI). Do NOT walk away at
armed:~/.cursor/skills/pr-land/scripts/pr-merge-watch.sh <repo#num> [more...] [--timeout 3600] # re-invoke on exit 7 (CONTINUE) until a terminal verdict; the 3600s budget spans the callsAct on the exit code — every non-zero exit is actionable, never a stop-and-wait:
- 0 ALL_MERGED → capture the merges, move on.
- 3 NEEDS_REBASE
<prs>→ the base moved (DIRTY, or BEHIND with green checks — GitHub auto-merge NEVER updates a BEHIND branch; unwatched it stalls forever). DISARM the listed PRs first (pr-land-automerge.sh --disarm): the re-push re-triggers the reviewer bots, and a live auto-merge races them perbot-thread-gate. Then loop them back through prepare (step 3; CHANGELOG conflicts resolve viachangelog-union-merge.sh) and push (step 4), re-run the step 2 comment check once their bot check-runs complete, and re-arm through step 5.2. Re-invoke the watcher. - 4 CHECK_FAILED
<pr>→ report the failing check, leave auto-merge armed unless the user says to disarm, keep watching the others. Do not local-merge around a red check. - 5 TIMEOUT → CI still running; re-invoke to keep watching.
- 6 BLOCKED_ON_REVIEW
<prs>→ every check on HEAD is green; the only unmet branch-protection requirement is an approving review. With Force Land authority, admin-merge perforce-land-review-bypassand disclose it; otherwise leave auto-merge armed and report the PR as waiting on human review.
Only finalize the land once every armed PR has either merged or been reported as blocked.
FALLBACK: local rebase + verify + merge. Use the local path ONLY when auto-merge is unsupported/blocked, when a rebase CONFLICT needs local resolution (per code-conflicts), or when the user explicitly asks for an immediate local merge. Run Steps 3-4 (prepare/push) first, then:
echo '[{"repo":"...","prNumber":123,"branch":"<prefix>/..."}]' | ~/.cursor/skills/pr-land/scripts/pr-land-merge.sh [method]
The local merge script processes PRs sequentially with automatic rebase-before-merge:
- Check if already merged — skip (handles re-runs after CHANGELOG resolution)
- Fetch + rebase onto upstream — ALWAYS done, even for first PR
- Conflict handling during rebase:
- No conflict → continue
- CHANGELOG-only (any section) → exit 4 (agent resolves, re-runs)
- Code conflict → skip PR, abort rebase, continue
- Push
--force-with-lease - Run local verification (MANDATORY)
- Merge via GitHub API
Exit codes:
0= All (non-skipped) PRs merged1= Verification failed4= Conflict needs resolution (rebase left in progress) — CHANGELOG-only OR code
On exit 4: Resolve per the conflict type (CHANGELOG → changelog-conflicts; code → code-conflicts, only if confidently determinable, else git rebase --abort and skip), push --force-with-lease, re-run merge. Script detects already-merged PRs and skips them.
GUI-dep check (owns it — steps 7 and 10 reference): the GUI's package.json is the source of truth for which repos are GUI dependencies — never assume every non-GUI repo publishes. Resolve per repo:
pkg=$(jq -r .name <repoDir>/package.json)
jq -e --arg p "$pkg" '.dependencies[$p] // empty' ~/git/edge-react-gui/package.json
Exit 0 → GUI dep: publish here, upgrade in step 7. Non-zero (not a dependency — e.g. a deployed server, typically also "private": true) → SKIP publish and step 7 for this repo entirely; its land is complete at merge (step 10 counts it fully landed then).
Ordering rationale (owns it — other steps reference, don't restate): git is retryable, npm is not. A pushed version commit that npm lacks is benign — npm publish completes it any time later, no history rewrite. A published npm version whose commit was never pushed is the unrecoverable direction (same-version republish is forbidden even after unpublish). So push BEFORE publish, and gate only the publish on the user's auth link. No y/N confirmations anywhere in this step: the land-and-publish request implies the push, and completing the AUTH_URL link is the publish confirmation (per npm-publish-auth).
For each ready repo, in sequence:
-
Bump + commit + tag (local):
echo '[{"repo":"...","branch":"master"}]' | ~/.cursor/skills/pr-land/scripts/pr-land-publish.shExit codes:
0= bumped/committed/tagged (needs push),1= verification failed (report),2= no unreleased changes. Idempotent resume on exit 2: if the version at HEAD is already bumped but missing from npm (a prior run's publish was abandoned), skip the bump and go straight to sub-step 3 —npm-publish-web.shdetects an already-published version and exits 0. -
Push master + tag:
cd <repoDir> && git push origin master && git push origin v<version> -
Publish (auth link = the confirmation): run in the background and relay every
AUTH_URLline to the user the moment it appears (push notification in orchestrated runs — never Slack):~/.cursor/skills/pr-land/scripts/npm-publish-web.sh <repoDir>The script preflights
npm whoami, web-logs-in first if needed (login and publish are separate auth events — seenpm-publish-auth), publishes, and printsPUBLISHED <name>@<version>. Exit codes:0= published,1= npm exited on its own without publishing, including after a completed auth (STOP; npm's own output is on stderr and the per-attempt captures are kept in the printed work dir; git stays as-is and the version is resumable),2= auth never completed (report; resume later via the idempotent path above),3= terminal registry rejection (permission, payment, version conflict),4= the packed tarball shrank vs the previous release,5= npm accepted the publish but the registry has not served the version yet. Only an expired or unused link consumes an attempt; the script re-checks the registry with a replication settle (--settle, default 600 s) before every new link. Exit 5 is never a re-publish. npm printed its own+ <pkg>@<version>line, so the version is taken for good and the registry is only lagging: re-run the same script to re-check (it exits 0 once the version is served), and never bump to a new version to get around it.
After all repos publish successfully, proceed to step 7 automatically — the exit codes are the confirmation.
cd <gui-repo-dir>
# Stash any uncommitted working changes so the reset is safe
if ! git diff --quiet HEAD 2>/dev/null || ! git diff --cached --quiet HEAD 2>/dev/null || [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
git stash -u
fi
git checkout develop
git fetch origin develop
git reset --hard origin/develop
Stashes remain stashed — the user can restore them after the run.
- After all dependency upgrades succeed, show the created
developcommit SHA(s) to the user and ask for confirmation to land them:
This push is required before the workflow can treat GUI dependency updates as landed. Do NOT proceed to staging cherry-pick or Asana updates until the~/.cursor/skills/git-branch-ops.sh push --branch developdeveloppush is confirmed complete.
At this point, origin/develop contains the new dep-upgrade commits from step 7, so each GUI PR will rebase cleanly onto a develop that already has its new dep versions.
Re-run steps 3, 4, and 5 against guiPrs:
- Feed
guiPrsintopr-land-prepare.sh(same invocation shape as step 3). - On CHANGELOG conflict:
changelog-union-merge.sh <repoDir> --continue, re-run prepare — same flow as step 3's CHANGELOG sub-step. - For each prepared GUI branch, push with
~/.cursor/skills/git-branch-ops.sh push --force-with-lease --branch <branch>(step 4). - Feed
guiPrsintopr-land-merge.sh(step 5).
Do NOT re-enter steps 6 or 7 — GUI does not publish to npm and has no deps of its own to upgrade.
Qualification is mechanical: the task's Build field from discovery, OR any prepared[i].newEntrySections entry (from the step 3 or step 8 prepare output for that PR) containing (staging). Do not re-derive it by reading the CHANGELOG; the prepare output is the record. A GUI PR that qualifies on either signal and is not cherry-picked is an unfinished land, whatever its Asana state. On disagreement (field staging, entry under ## Unreleased), the placement question was already surfaced in step 3 — the field wins for routing.
Skip this step entirely only when NO commit qualifies on either signal.
For qualifying PRs/commits, invoke the /staging-cherry-pick skill:
echo '[{"repo":"edge-react-gui","prNumber":123,"mergeSha":"abc123"}]' | ~/.cursor/skills/staging-cherry-pick/scripts/staging-cherry-pick.sh
Pass the mergeSha from the merge step's JSON output. For dep upgrade commits, pass the commit SHA from step 7 (the script handles single-parent commits by cherry-picking the sha itself). Staging takes cherry-picks ONLY — never run upgrade-dep.sh on staging or commit natively there (staging-cherry-pick cherry-picks-only).
On exit 3 (CHANGELOG conflict): Run changelog-union-merge.sh <repoDir> --continue (it detects the in-progress cherry-pick). Resolve by hand (existing staging entries first, then the new entry) only if it exits non-zero. Re-run for remaining PRs.
On exit 1 (code conflict): STOP and report to user.
After cherry-picks succeed, ask user to confirm push:
git push origin staging
Then restore the previous branch.
Only update for fully landed PRs:
- GUI PRs: merged
- GUI-dep repos (per step 6's GUI-dep check): merged AND published AND GUI deps updated
- Non-dep repos (fail the GUI-dep check, e.g. deployed servers): merged
Do NOT update for: skipped PRs, addressed-but-not-re-reviewed PRs, or GUI-dep repos not published.
printf '[{"repo":"edge-react-gui","prNumber":123}]' | ~/.cursor/skills/pr-land/scripts/pr-land-extract-asana-task.sh > /tmp/asana.json
The helper outputs JSON like { "tasks": [{ "taskGid": "...", "label": "repo#123" }], "missing": [{ "label": "...", "reason": "..." }] }.
Parent-walking: taskGid is the PR's linked task's PARENT when a parent exists (the feature-level task that represents the unit of work across repos). Standalone tasks (no parent) return themselves. Only updates the parent — leave subtasks alone; they have their own state that is managed separately. Sibling subtasks of the same parent dedupe to one entry; label lists all contributing PRs (e.g. "edge-react-gui#123, edge-core-js#456").
Review the missing array, report any entries lacking an Asana link, and skip those PRs for Asana updates.
~/.cursor/skills/asana-task-update/scripts/asana-task-update.sh \
--task <task_gid> \
--set-board-state "QA Verification" \
--unassign
Writes to the new Board State 🤖 field. The legacy Status field is no longer updated.
Exit codes per call:
0= success1= error2= needs user input
Fully landed: ✓ # () — merged, cherry-picked to staging, Asana → QA Verification ✓ # () — merged, Asana → QA Verification ✓ # () — merged, published v, GUI deps updated, Asana → QA Verification
Addressed but needs re-review: ⚠ # () — fixup pushed, awaiting review
Skipped (conflicts): ⚠ # () — stale / code conflict in
Not published (outstanding PRs): ⚠ — N PRs skipped, publish deferred
</step>
<conflict-handling description="Summary of conflict types and resolution.">
| Conflict Type | Script Behavior | Agent Action |
|---|---|---|
| Code files | Rebase left in progress | Resolve semantically when determinable (keep both sides, regen lockfiles), `git rebase --continue`, re-run; `git rebase --abort` + skip only if not determinable |
| CHANGELOG only (prepare) | Report conflict | Resolve semantically, re-run prepare |
| CHANGELOG only (merge) | **exit 4** with instructions | Resolve semantically, push, re-run merge |
Both prepare and merge scripts can detect CHANGELOG-only conflicts. In either case:
1. Script outputs clear resolution instructions
2. Agent resolves semantically (upstream entries first)
3. `git add CHANGELOG.md && GIT_EDITOR=true git rebase --continue`
4. Push with `~/.cursor/skills/git-branch-ops.sh push --force-with-lease --branch <branch>`
5. Re-run the script to verify and proceed
</conflict-handling>
<changelog-resolution description="How the agent resolves CHANGELOG conflicts.">
Typical conflict:
<<<<<<< HEAD
- added: Feature from upstream =======
- changed: Our feature
our-commit
Resolution: Upstream first, then ours:
- added: Feature from upstream
- changed: Our feature
<sub-step name="During prepare (no push yet)">
1. Read CHANGELOG.md with conflict markers
2. Resolve semantically using StrReplace
3. `git add CHANGELOG.md && GIT_EDITOR=true git rebase --continue`
4. Re-run `~/.cursor/skills/pr-land/scripts/pr-land-prepare.sh`
</sub-step>
<sub-step name="During merge (already pushed, GitHub reports conflict)">
1. `cd <repoDir>`
2. `git fetch origin && git rebase origin/master` (or `origin/develop`)
3. Read CHANGELOG.md with conflict markers
4. Resolve semantically using StrReplace
5. `git add CHANGELOG.md && GIT_EDITOR=true git rebase --continue`
6. `~/.cursor/skills/git-branch-ops.sh push --force-with-lease`
7. Re-run `~/.cursor/skills/pr-land/scripts/pr-land-merge.sh` — verification runs automatically
</sub-step>
Verification checks: no conflict markers remaining, proper entry format (`- type: description`), no malformed entries. If verification fails after resolution, the script prompts the user.
</changelog-resolution>
<code-conflict-resolution description="How the agent resolves a code (non-CHANGELOG) conflict left in progress by prepare/merge. Governed by the `code-conflicts` rule: resolve only when confidently determinable, else abort + skip.">
The rebase is paused with markers in the conflicted files. Decide FIRST whether the conflict is confidently resolvable (both sides have independent intent you can preserve) or a guess (overlapping logic in the same function). If a guess → `git -C <repoDir> rebase --abort` and skip the PR.
If resolvable, for EACH conflicted file:
1. Read it and resolve the markers so BOTH sides' intent survives. Common determinable shapes:
- **Dependency / version bump vs our removal/edit**: take the upstream version pin AND apply our add/remove. (e.g. keep upstream's bumped `rollup`, drop the package our branch removed.)
- **Upstream deleted a file we modified** (or vice versa): take the deletion when it is intentional upstream (e.g. a lockfile dropped in a yarn→npm conversion) — `git rm <file>`.
- **Non-overlapping edits in the same file**: keep both hunks.
2. If `package.json` dependencies changed, regenerate the lockfile so it matches: `npm install` (npm repos) or `yarn install` (yarn repos) — never hand-merge a lockfile. Stage the regenerated lockfile.
3. `git -C <repoDir> add <files> && GIT_EDITOR=true git -C <repoDir> rebase --continue`
4. Re-run the script (`pr-land-prepare.sh` / `pr-land-merge.sh`). Re-verification is mandatory and catches follow-on issues a resolution can introduce (e.g. a removed import leaving a formatting violation → fix with `eslint --fix` on the file, amend, re-run).
</code-conflict-resolution>
<safety-guarantees>
1. Code conflicts resolve or skip cleanly — scripts leave the rebase in progress for semantic resolution when determinable; the agent aborts + skips (no dirty state) only when not confidently resolvable
2. CHANGELOG conflicts are scripted — agent resolves semantically (any section including staging), verification validates
3. Verification is mandatory — the default path gates locally (Step 3 `verify-repo.sh`) before arming, then watches GitHub's required checks through to a merge; the local fallback builds verification into the merge script, physically blocking merge on failure
4. Pre-merge is safe — can force-push as many times as needed
5. Sequential merging with auto-rebase — each PR rebased onto updated base
6. No bypasses — scripts enforce rules, agent cannot skip steps
7. Unexpected errors halt execution — un
*Truncated - read the full file at https://github.com/EdgeApp/edge-dev-agents/blob/ffe3df0961d16475938f63a838b2b2d6ed0e425d/.cursor/skills/pr-land/SKILL.md.