T-2WF7-entities-migrate-route-via-inflight-prs
Status: closed/superseded · Impact: medium · Complexity: small
Today, /sdlc:entities-migrate is blind to in-flight PRs: it always opens a new
PR from main carrying the mechanical schema bumps, even when the drifting
entity file is already being modified by an open task PR. That guarantees the
two PRs collide on the same frontmatter and forces a manual rebase on whichever
merges second. The proposed redesign routes the bump onto the existing PR’s
task branch when one exists (via the same lease re-acquire dance
/sdlc:pr-respond already uses), and falls back to the current new-PR behaviour
only for drifting files not touched by any open SDLC PR.
Originating from 2026-05-27-ci-workflow-overhaul in
template-tauri-nuxt:
When
/sdlc:setupran the audit after atask-workPR was already open againstawaiting-review, the audit reportedauto_fixable=truedrift (task schema v3 → v4) on the task file the PR was modifying. Running/sdlc:entities-migratethen would have opened a second PR mutating the same frontmatter — guaranteed merge conflict. The cleanest fix that doesn’t exist yet is forentities-migrateto detect the in-flight PR and push the bump onto its branch through the existing lease coordination.
| Location | Role today |
|---|---|
plugin/skills/entities-migrate/SKILL.md | The skill spec. Step 3 unconditionally creates one worktree off main (git worktree add .sdlc/worktrees/entities-migrate -b chore/entities-migrate main); Step 6 unconditionally opens one PR off that branch. No PR discovery anywhere. |
plugin/skills/entities-migrate/migrate_entities.ts | The deterministic tail. Pulls the audit report, applies each auto_fixable=true fix to files under docs/planning/<plural>/, prints a summary. Operates on whatever project root it is pointed at; has no concept of “this file is also open in a PR.” |
plugin/skills/pr-respond/SKILL.md | The lease re-acquire dance this task reuses: Step 1 parse-footer, Step 2 cd into .sdlc/worktrees/<task_id>, Step 3 fencing check (inspect lease, compare lease_id), Step 4 task reacquire --phase responding, Step 5 heartbeat, Step 8 git push, Step 9 task transition --phase awaiting-review. |
plugin/scripts/sdlc_lease.ts | The lease CLI. parse-footer <pr>, inspect <ref> --json, task reacquire <id> --phase <p>, task transition <id> --phase <p> --handoff-* are the subcommands the routed path calls — all already exist (pr-respond uses every one). |
plugin/skills/pr-check/SKILL.md | Defines the open/closed PR verdict semantics the routed path mirrors when deciding which PRs are eligible targets (skip CLOSED/MERGED, same as pr-respond’s caller). |
Proposed
Section titled “Proposed”Split /sdlc:entities-migrate’s post-audit step into a per-drifting-file
routing decision instead of one monolithic worktree+PR. For each file the
audit flagged auto_fixable=true, ask: is this file modified by an open SDLC
PR right now? If yes, push the bump onto that PR’s task branch through the
lease protocol pr-respond already implements. If no, the file flows through
the existing new-PR codepath unchanged. The change is almost entirely in
SKILL.md prose (the skill’s procedure) plus a thin PR-discovery helper; the
mechanical fix logic in migrate_entities.ts is reused verbatim — it already
takes a project root, so pointing it at an in-flight worktree instead of the
dedicated entities-migrate worktree requires no new fix code.
Approach
Section titled “Approach”- Discover in-flight SDLC PRs once, up front. After Step 2’s audit
dry-run produces the set of
auto_fixable=truedrifting files, list open PRs viagh pr list --state open --json number,headRefName,body,files. Keep only PRs whose body carries a parseable lease footer — reusebun run ${CLAUDE_PLUGIN_ROOT}scripts/sdlc_lease.ts parse-footer <pr>(exit 0 = footer present, exit 5 = no footer → skip the PR). Build a mapdrifting_file → owning_prby intersecting each PR’sfileslist with the drifting set. A file appears in at most one map entry (a file open in two PRs is out of scope — see Out of scope). - Partition the drifting set into
routed(files with an owning PR) andresidual(files with no owning PR). - For each owning PR in
routed, run the lease dance exactly as/sdlc:pr-respondSteps 1–9, substituting the bump for the response work: a.cd .sdlc/worktrees/<task_id>(the worktree task-work created; if absent emitWORKTREE-MISSING task=<task_id>and demote that PR’s files toresidual). b. Fencing check:sdlc_lease.ts inspect refs/sdlc/tasks/<task_id> --json, compare itslease_idagainst the footer’s. On mismatch, emitLEASE-FENCING-MISMATCHand demote the files toresidual(do not steal a rotated lease). c.sdlc_lease.ts task reacquire <task_id> --phase responding(reuse the existingrespondingphase — do NOT add a newmigratingphase; see Notes). On exit 4 (LEASE-CONFLICT) skip this PR, leave its files unrouted, and report the conflict; another worker holds it. d. Apply the bump in the worktree: runbun run ${CLAUDE_PLUGIN_ROOT}skills/entities-migrate/migrate_entities.ts --project-root .sdlc/worktrees/<task_id> --type <type>restricted to the owned file’s type.migrate_entities.tsis reused unchanged. e. Commit ontask/<task_id>with subjectchore(entities): apply auto-fixable schema drift(use themktemp+ quoted-heredoc +git commit -Fpattern perplugin/conventions/commit-messages.md);git push. f.sdlc_lease.ts task transition <task_id> --phase awaiting-reviewwith the five--handoff-*flags, mirroring pr-respond Step 9. Post a one-line self-comment via${CLAUDE_PLUGIN_ROOT}skills/pr-check/post_self_comment.sh <pr> <body>so the PR author sees the bump landed and the pr-check cursor does not re-fire NEEDS-RESPONSE on our own push. - For
residual, run the existing codepath verbatim: create thechore/entities-migrateworktree offmain(Step 3), migrate only the residual files, open one PR (Step 6). Ifresidualis empty, skip the worktree and PR entirely. - Document the heartbeat pairing (pr-respond Steps 5/7): start
lease_heartbeat_loop.tsbefore the sub-step-3d migrate and always stop it after, so a slow bump does not let the lease expire mid-push. - Update the skill’s reference block and Notes to cite
plugin/conventions/lease-aware-skills.md(the shared re-acquire pattern) rather than re-inlining pr-respond’s prose, per the no-duplicate-prose rule inplugin/skills/CLAUDE.md.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/entities-migrate/SKILL.md | modify | Insert the per-file routing decision between Step 2 (audit dry-run) and Step 3 (worktree). Add the lease-dance subsection (mirroring pr-respond Steps 1–9 by reference, not by copy), the routed/residual partition, the per-PR fencing + reacquire + push + transition + self-comment flow, and the heartbeat pairing. Make Step 3/6 (worktree + new PR) conditional on a non-empty residual set. |
plugin/skills/entities-migrate/invariants.yaml | modify | Add invariants pinning the new behaviour: the SKILL.md must reference parse-footer, task reacquire, the responding phase, post_self_comment.sh, and lease-aware-skills.md; and must NOT introduce a new migrating lease phase. |
plugin/conventions/lease-aware-skills.md | modify | Add /sdlc:entities-migrate to the list of skills that adopt the re-acquire-into-responding pattern, so the shared convention names its new consumer. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
plugin/skills/entities-migrate/SKILL.mdcontains a step that, before creating thechore/entities-migrateworktree, partitions the audit’sauto_fixable=truefiles into a routed set (files modified by an open lease-footer-bearing PR) and a residual set, usinggh pr list ... --json number,headRefName,body,filesandsdlc_lease.ts parse-footer. - AC-2: For the routed set, the SKILL.md prescribes the pr-respond lease sequence by reference —
parse-footer→cdworktree →inspectfencing check →task reacquire --phase responding→ migrate-in-worktree → commit ontask/<task_id>→git push→task transition --phase awaiting-review— and explicitly states no second PR is opened for a routed file. - AC-3: The SKILL.md states the residual codepath is the unchanged Step 3/6 (worktree off
main+ one PR), and that the worktree and PR are skipped entirely when the residual set is empty. - AC-4: The SKILL.md prescribes CAS-guarded coordination identical to pr-respond: on
LEASE-CONFLICT(reacquire exit 4) the PR is skipped and its files left unrouted; onLEASE-FENCING-MISMATCHthe lease is not stolen and the files are demoted to residual. - AC-5: The routed path reuses
migrate_entities.tswith--project-rootpointed at the in-flight worktree and reuses therespondinglease phase; the SKILL.md introduces no new fix logic and no new lease phase (verifiable: nomigratingphase token appears in SKILL.md orsdlc_lease.ts). - AC-6:
plugin/skills/entities-migrate/invariants.yamldeclares the new invariants andbun run .claude/skills/project-check/check_skill_prose.tspasses against the updated SKILL.md + invariants pair. - AC-7:
plugin/conventions/lease-aware-skills.mdlists/sdlc:entities-migrateas a re-acquire-pattern consumer, and the SKILL.md references that convention doc rather than inlining pr-respond’s prose (no verbatim re-copy of the lease steps).
Out of scope
Section titled “Out of scope”- Bumping
auto_fixable=falsedrift onto in-flight PRs — only mechanical fixes auto-route; ambiguous drift still surfaces for human review in the residual PR’s “Manual review needed” section. - Closed-but-not-merged and merged PRs — skipped, same as
/sdlc:pr-check’s action semantics. - A drifting file modified by two open PRs simultaneously — left in the residual set and reported, not split across both branches.
- Adding a new
migratinglease phase — the existingrespondingphase is reused (a bump is a flavour of automated response to drift; a parallel phase would fork the lease state machine for no protocol gain). - Changing
migrate_entities.ts’s fix logic — it is reused verbatim via--project-root.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-05-28 UTC from
2026-05-27-ci-workflow-overhaul in
https://github.com/sksizer/template-tauri-nuxt. The originating task’s
/sdlc:setup post-merge re-audit surfaced auto-fixable schema drift on the
task file that the originating PR was itself modifying, which made the gap
in entities-migrate’s design visible.