p-wegner

merge-reconciler

Land a whole BATCH of stranded/mergeable-but-conflicting workspaces onto the base branch in the most efficient way. You are handed the full batch as injected JSON ({{strandedBatch}}), analyze the whole set first (clean-independent / file-overlap clusters / migration collisions / dependencies), then land everything via the board's SAFE primitives — never manual git in the main checkout. Resolve each overlapping cluster's union ONCE in an integration worktree instead of N re-conflicting rebases. Report what landed and what was escalated.

p-wegner 9 2 Updated 4mo ago
GitHub

Install

npx skillscat add p-wegner/agentic-kanban/merge-reconciler

Install via the SkillsCat registry.

SKILL.md

merge-reconciler

You are the batch merge-reconciler. The in-process auto-merge orchestrator already landed every clean, independent workspace it could. What's left — handed to you — is the residue that conflicts, overlaps, is stale, or collides on migration numbers. Land that whole residue onto the base branch as efficiently and safely as possible, then report.

You run inside one workspace's git worktree (the "integration worktree") — a full isolated checkout where running git is SAFE. The MAIN checkout is NOT yours to touch.

Inputs (injected — do not re-derive)

  • {{baseBranch}} — common target branch (e.g. master).
  • {{projectId}} — the project.
  • {{serverPort}} — board server port (default 3001) for REST calls.
  • {{integrationWorkspaceId}} / {{integrationWorkingDir}} — the workspace + worktree you run in (orchestrator picked the least-overlapping member). All git resolution happens HERE.
  • {{strandedBatch}} — the full batch as JSON from mergeQueueService.computePlan(...). Trust it; it's ground truth:
{
  "baseBranch": "master", "projectId": "…",
  "order": [            // suggested landing order, least file-overlap first
    { "workspaceId": "…", "issueId": "…", "issueNumber": 123, "issueTitle": "…",
      "branch": "feature/ak-123-…", "workingDir": "C:\\…\\worktree", "baseBranch": "master",
      "repoPath": "C:\\…\\repo", "isDirect": false, "status": "idle", "readyForMerge": true,
      "changedFiles": ["packages/server/src/x.ts", "packages/shared/drizzle/0061_foo.sql"] }
  ],
  "overlaps": [ { "workspaceIdA": "…", "workspaceIdB": "…", "overlapCount": 3, "files": ["…"] } ],  // pairwise shared files = cluster signal
  "totalOverlapScore": 7,
  "migrationCollisions": [   // same drizzle NNNN used by >1 workspace
    { "migrationNumber": "0061", "workspaces": [ { "workspaceId": "…", "issueNumber": 123, "issueTitle": "…", "files": ["packages/shared/drizzle/0061_foo.sql"] } ] } ],
  "conflictPreviews": [ { "workspaceId": "…", "hasConflicts": true, "conflictingFiles": ["…"], "isStale": false } ],  // read-only merge-tree probe
  "dependencies": [ { "issueId": "…", "issueNumber": 123, "dependsOn": [ { "issueNumber": 120, "type": "depends_on" } ] } ]
}

If {{strandedBatch}} is empty or one element with no conflicts, do the trivial thing (land it, or report nothing) and stop.

Hard safety rails — non-negotiable

  1. NEVER run git in the main checkout (repoPath) — no merge/reset/rebase/push/commit there. The only sanctioned writer of the base ref is the board's merge primitive. Manual git is allowed only inside a worktree (workingDir of a member, or {{integrationWorkingDir}}).
  2. Land ONLY via board primitives, preference order:
    • REST POST http://localhost:{{serverPort}}/api/workspaces/:id/merge — goes through the locked, dirty-main-guarded merge service (preferred).
    • MCP mcp__agentic-kanban__merge_workspace { workspaceId } — works but takes no per-repo lock; use only when landing strictly one workspace at a time.
    • REST POST /api/workspaces/:id/reconcile-as-done — close a sibling whose branch is already an ancestor of base (use after a cluster land).
    • REST POST /api/workspaces/:id/update-base { "mode": "rebase" } / .../abort-rebase — (re)base a single worktree onto base.
  3. Idempotent + never leave base mid-merge. merge is plumbing-based and idempotent (no-op if already an ancestor). Verify after every land (Step 5). On failure, ABORT cleanly (abort-rebase, or git rebase/merge --abort in the worktree) so nothing is left half-merged.
  4. Serialize landings — one at a time; never fire two /merge calls in parallel.
  5. Do NOT hand-renumber migrations — the board auto-renumbers on rebase/merge. You only ORDER colliding ones to land sequentially (Step 3).
  6. Respect dependency order — if A depends_on B and both are batched, land B first.
  7. Attempt cap: 2 per workspace/cluster. Still failing after 2 → ESCALATE (Step 6), don't loop.
  8. If base isn't checked out in the main checkout (a /merge returns CONFLICT saying main HEAD is on a different branch), STOP and escalate the whole batch — a human has the main checkout on a feature branch.

Playbook

Step 0 — Confirm your worktree

git rev-parse --show-toplevel + git status in CWD. Confirm you're in {{integrationWorkingDir}}, not repoPath. Same path → STOP, report "integration workspace is direct/main-checkout — cannot reconcile safely."

Step 1 — Analyze the whole batch FIRST (no edits)

Classify every workspace into exactly one bucket; write the plan before acting:

  • CLEAN — hasConflicts === false AND isStale === false AND in no overlaps entry with overlapCount > 0. (Stragglers the orchestrator left — land in Step 2.)
  • STALE-ONLY — isStale === true, hasConflicts === false, no real overlap. Plain rebase onto base fixes it (Step 2b).
  • CLUSTER — connected component in overlaps (each overlapCount > 0 is an edge; transitively-connected workspaces = one cluster). Resolve together ONCE (Step 4).
  • MIGRATION-COLLISION — any workspace under migrationCollisions[*].workspaces. Land sequentially so the board auto-renumbers each above the last (Step 3). A workspace can be both a cluster member and a collider — handle the cluster first, then sequence.

Honor dependencies within any ordering. Print the plan: buckets, clusters (member #Ns), migration groups, final landing sequence.

Step 2 — Land CLEAN + STALE-ONLY stragglers first

CLEAN, in order:

curl -s -X POST http://localhost:{{serverPort}}/api/workspaces/<id>/merge

STALE-ONLY (2b) — rebase via the board, then merge:

curl -s -X POST http://localhost:{{serverPort}}/api/workspaces/<id>/update-base -H "Content-Type: application/json" -d '{"mode":"rebase"}'
curl -s -X POST http://localhost:{{serverPort}}/api/workspaces/<id>/merge   # if rebase returned ok

Verify each (Step 5) before moving on. If a "clean" one now conflicts (base moved under it), demote it into the relevant cluster (Step 4).

Step 3 — Sequence MIGRATION-COLLISION groups

Per NNNN group: land ONE first (prefer lowest issueNumber or the dependency-root) via Step 2 or its cluster (Step 4). Auto-renumber runs on the NEXT workspace's rebase/merge and bumps its migration above the just-landed one. So just go in sequence — re-checking previews is unnecessary. Never edit NNNN_*.sql or _journal.json by hand.

Step 4 — Resolve each CLUSTER's union ONCE (the core efficiency win)

There's no integration/octopus/squash primitive here — do it manually, ONLY inside a worktree. The cluster's integration worktree = {{integrationWorkspaceId}} if it's in this cluster, else the cluster member with least overlap (first in order among the cluster). Per cluster:

  1. cd into that member's workingDir; confirm it's a worktree (git rev-parse --show-toplevel ≠ repoPath).
  2. Bring base in first so you resolve against current base:
    git fetch --all --prune        # safe in a worktree
    git rebase <baseBranch>        # or: git merge <baseBranch>
    Resolve conflicts, git add -A, git rebase --continue (or commit the merge).
  3. Bring in EACH other cluster member's branch, resolving the union of conflicts once:
    git merge --no-ff <other-feature-branch>   # repeat per other cluster member
    CRITICAL: always --no-ff, NEVER --squash. A squash repackages the other branch's commits into a new commit that is NOT a descendant of that branch — so checkAlreadyMerged (verifies the sibling's HEAD is reachable from base) returns isAlreadyMerged: false even after landing, making reconcile-as-done fail and stranding the sibling. --no-ff preserves the originals as ancestors so isAncestor succeeds. (Small independent commits may be git cherry-picked instead — your choice; goal is ONE combined branch with the cluster's union, resolved a single time.) Resolve markers, git add, commit.
  4. Make the build sane (no markers; colliding migrations within the cluster get renumbered by the board, not by hand). Commit everything — git status clean.
  5. Land the combined branch (the only write to base):
    curl -s -X POST http://localhost:{{serverPort}}/api/workspaces/<integrationWorkspaceId>/merge
    If /merge says "not approved for merge" (readyForMerge=false), mcp__agentic-kanban__mark_ready_for_merge { workspaceId } then re-call /merge.
  6. Reconcile sibling members as Done. Their commits are now ancestors of base. Wait for the integration /merge to return 200 first (the merge must fully commit before the ancestry check passes), then per sibling:
    curl -s http://localhost:{{serverPort}}/api/workspaces/<siblingId>/already-merged-status
    curl -s -X POST http://localhost:{{serverPort}}/api/workspaces/<siblingId>/reconcile-as-done   # only if isAlreadyMerged === true
    If already-merged-status says NOT merged, that sibling didn't land (most often because you used --squash — see 4.3); do NOT reconcile, escalate via Step 6. reconcile-as-done re-runs checkAlreadyMerged server-side (a second guard, not a bypass) — a 400 "Branch is not fully merged" means trust it, don't force-close.

Repeat per cluster. Attempt cap: 2 per cluster.

Step 5 — Verify each landing (base actually advanced)

After every /merge or cluster land:

curl -s http://localhost:{{serverPort}}/api/workspaces/<id> | jq '.status, .mergedAt'
# MCP: mcp__agentic-kanban__get_issue { issueId } → status should be "Done"

Merged but issue not Done, or mergedAt null → treat as FAILED, escalate (don't retry past the cap).

Step 6 — Escalate what you couldn't land

For anything unresolved within the cap (true conflict, sibling not actually merged, base not checked out, infeasible):

  • Move its issue back to In Progress via MCP move_issue (so it's visibly needing work), OR leave untouched if already In Review.
  • Record why: MCP create_diff_comment or a short "reconciler could not land: " in the description.

Do NOT close, cancel, or reconcile-as-done anything you did not actually land.

Step 7 — Final report

End with a concise summary (your last message):

RECONCILER REPORT (base: <baseBranch>)
Landed clean/independent: #a, #b
Clusters resolved (union once): [#c + #d → combined on <branch>], …
Migration groups sequenced: 0061 → #e then #f (auto-renumbered)
Reconciled-as-done siblings: #g
Escalated to human: #h (reason: …), #i (reason: …)
Base advanced: yes  (verified <N> issues now Done)

Then exit. The orchestrator polls your session and re-scans on exit — do not loop or relaunch yourself.