| name | merge-discipline |
| version | 1.5.0 |
| user-invocable | true |
| description | Hard gate before merging into a protected branch: run Parts A→B→C→R→D (OpenSpec archive association, rebase/conflict pre-check, coverage preference, pr-code-review with optional light depth via pr-review-gate, tip-pin merge). Do NOT merge while an associated OpenSpec change is still active; do NOT skip on a direct "merge MR" command. Triggers — 「合并 tip」「merge tip」「合并纪律」「push 后合并」「archive 合入」「合并前门控」「rebase 检查」「冲突预检」「合并前 rebase」「先 archive 再 merge」「合并前 code-review」 / merge discipline, archive-before-merge, rebase pre-check, coverage gate, pr code review before merge. |
| dependencies | ["pr-code-review"] |
Merge Discipline
Internal shared skill — the single source of truth for merge-time discipline. Five parts, run in order A → B → C → R → D: OpenSpec archive association gate (Part A) + rebase/conflict pre-check (Part B) + coverage gate (Part C) + PR code review (Part R, strong-dep pr-code-review) + tip pinning (Part D). Referencing workflows declare this in frontmatter dependencies and abort at startup if missing.
Prerequisite skill check
On load, scan frontmatter dependencies. If any is missing → print the Missing Notice below and abort (no silent degrade).
Missing Notice
⚠️ merge-discipline is missing a strong dependency and cannot run
【Missing skill(s)】
- <name>: <one-line purpose>
【Why it's needed】
merge-discipline strongly depends on:
- `pr-code-review`: Part R — multi-perspective PR review with ≥80 confidence filter before tip-pin merge
【Install】
Install each missing skill by name (preferred):
npx skills add FuDesign2008/open-skills -g --skill <name> --yes
Example for this skill's dependency:
npx skills add FuDesign2008/open-skills -g --skill pr-code-review --yes
Or install every open-skills skill:
npx skills add FuDesign2008/open-skills -g --skill '*' --yes
Re-trigger merge after installing.
When listing multiple missing skills, print one npx skills add … --skill <name> --yes line per missing name (do not only show '*').
When this applies
Any merge into a protected branch (glab mr merge / gh pr merge / git merge <target>) — whether from a workflow's branch-closeout decision, a direct user merge command ("merge MR" / "合并"), or AI preparing the merge call. "Keep branch" / "continue development" do not trigger.
When hosts use feature-branch-closeout, that skill owns the closeout menu; this skill runs only after merge is selected (or on a direct merge command). Do not redefine the full menu here.
Execution order when merging: Part A (archive gate) → Part B (rebase) → Part C (coverage) → Part R (PR code review) → Part D (tip pinning) → merge. Part A runs first so archive work is not deferred past rebase/CI. A rebase changes the source tip, forcing Parts C/R/D to re-run on the new tip.
Part A — OpenSpec archive association gate
Prevents merge-then-archive: implementation lands on the protected branch while OpenSpec delta specs / archive still live only on a follow-up docs PR.
Association (either hit = associated)
- Diff hit: the PR or branch diff includes paths under
openspec/changes/<name>/ that are not under openspec/changes/archive/.
- Session hit: a session-bound OpenSpec change name still appears as active in
openspec list (not archived).
Decision matrix
| State | Action |
|---|
| Not associated | Pass through → Part B |
Associated and change already archived (only under openspec/changes/archive/…, absent from openspec list) | Pass through → Part B |
| Associated and change still active | Block merge. Name the change(s). Require archive (sync main specs + move to openspec/changes/archive/) on the same source tip, then re-enter merge from Part A. Do not run Part B/C/D or the merge command. |
Hard rules
- Direct user merge commands MUST run this Part — no implicit skip.
- User may force-continue only with explicit skip 留痕 (template below); default is block.
- Do not recommend a separate post-merge archive MR while the change is still associated and active (see Part D Strategy B — recovery only).
Detect (illustrative)
gh pr diff <id> --name-only | grep -E '^openspec/changes/[^/]+/' | grep -v '^openspec/changes/archive/'
openspec list --json
留痕 templates
Location: PR description and design.md Verification Notes (when present).
| Case | Template |
|---|
| User explicit skip | 【OpenSpec archive 门控跳过】用户显式跳过,关联 change 仍为 active。时间:<ISO>。决策人:用户。change:<name>。 |
| Implicit miss (merged without gate) | 【OpenSpec archive 门控漏跑】合并已发生但 Part A 未运行。时间:<ISO>。漏跑阶段:<合并前/合并后>。 |
Red flags
- Merging because "stage 8 already said archive later" or "we'll open a docs PR after merge".
- Skipping Part A on a direct "merge MR" while
openspec/changes/<name>/ is still in the PR diff.
- Treating Strategy B as the happy path for an open, associated MR.
Part B — Rebase / conflict pre-check
Prevents the blind-merge-into-a-moving-target failure: by the time the user says "merge", the target (release/*, main, …) has usually moved — teammates landed their own work. Merging without checking either fails at the platform (mergeable=false) or silently lands behind the tip. This Part makes the AI surface that and offer to rebase, instead of the user having to remember to ask.
Detect
git fetch origin <target-branch>
BEHIND=$(git rev-list --count HEAD..origin/<target-branch>)
BASE=$(git merge-base HEAD origin/<target-branch>)
CONFLICTS=$(git merge-tree "$BASE" HEAD origin/<target-branch> | grep -c "^changed in both")
<target-branch> = the MR/PR base (query it via your platform's CLI) or the merge target the user named.
Decision matrix
| State | Action |
|---|
BEHIND=0 and CONFLICTS=0 | Clean — proceed to Part C |
BEHIND>0 and CONFLICTS=0 | Report "target N commits ahead; rebase applies cleanly" → wait for user confirm |
CONFLICTS>0 | Report "target N ahead, ~M files conflict" → wait for user confirm |
No auto-rebase without confirmation — rebase rewrites the source branch and triggers a full CI rerun; the user owns that decision.
On confirm — execute rebase (source branch only)
The source is the user's personal fix branch (fix/jira-fix-…), which team policy allows to force-push. The protected target is never rewritten by this Part.
git rebase origin/<target-branch>
git push --force-with-lease origin <source-branch>
If git-conflict-resolve is unavailable or the user aborts, stop — leave the worktree mid-rebase (git rebase --abort to bail) for human resolution.
Scope boundary — this Part does NOT wait for CI
After --force-with-lease push, this Part ends: it reports "rebased, PR updated, CI rerunning" and returns control to the workflow. CI-gating and the actual merge stay with the workflow's normal closeout (Part C → Part D → merge) — the user comes back after CI is green, same as any post-review merge. This Part is a rebase repair tool, not a merge-through-CI orchestrator.
Loop bound
Each merge attempt triggers at most one rebase. If the target moves again while waiting for CI, the next merge attempt re-enters from Part A and re-detects — bounded, not infinite.
Red flags
- Merging without this Part because "CI is already green" (the green is on the old tip; target moved).
- Rebasing, then claiming merge done before Part D's ancestor check passes on the new tip.
Part C — Coverage gate
Part C starts on merge intent. It does not auto-run the analyzer by default.
1. Resolve project preference
Scan AGENTS.md then CLAUDE.md (first match wins) for a line matching (case-insensitive key):
coverage-gate:\s*(always|never|ask)\b
| Value | Behavior |
|---|
| (unset) | Treat as ask |
ask | On every merge, ask the user: run coverage for this merge, or skip? MUST NOT auto-run |
always | Run gate steps without asking (subject to analyzer availability) |
never | Skip analyzer; write project-preference 留痕; proceed to Part R |
2. Pre-detection (only if decision is run)
If test-coverage-analyzer is available, continue to gate steps. If not found, output "gate unavailable: test-coverage-analyzer not detected", write an environment-gap留痕, and let the user decide whether to proceed with merge.
3. Gate steps (only if decision is run)
Independent Bash permission — runs the analyzer script:
-
Construct --base (try in order, stop on first hit):
- MR/PR:
gh pr view --json baseRefName -q .baseRefName / glab mr view <iid> -F json | jq .target_branch → --base <target> (bare branch name; the script's validate_ref adds origin/)
- On failure / detached HEAD / no remote → omit
--base, rely on the script's 5-level fallback; warn "no explicit base, MR may misjudge as 0-diff"
- Multi-repo MR → run gate per-repo, each with its own
--base; any repo failing → overall pause
-
Call the script (read test-coverage-analyzer SKILL.md first to confirm param contract):
python3 "<SKILL_DIR>/scripts/analyze_coverage.py" "<project-root>" [--base <target>]
-
Decision matrix:
| Result | 🤖 Auto | 👤 Manual |
|---|
| ✅ Report generated + coverage meets threshold | Continue to Part R (PR code review) | Prompt pass, wait for user re-confirm |
| ⚠️ Coverage below threshold | Pause, output report, await user (force/add-tests/abort) | same |
| 💥 Crash / no report / exit 1 | Treat as gate-fail, pause | same |
| 📭 No test code / 0% pass | Present report, pause for user judgment | same |
| 🕳️ Should-run but gate not run and merge happened (implicit miss) | Pause merge, rerun gate; if already merged, write miss留痕 | same |
Should-run = preference always, or preference ask after the user chose run. Preference never or user-explicit skip under ask are not implicit misses.
留痕 templates
Location: PR description and design.md Verification Notes.
| Case | Template |
|---|
| User explicit skip | 【覆盖率门控跳过】用户显式跳过,未运行 test-coverage-analyzer。时间:<ISO>。决策人:用户。 |
| Project preference never | 【覆盖率门控跳过】工程偏好 coverage-gate: never。时间:<ISO>。决策人:项目配置。 |
| Env gap (skill not found) | 【覆盖率门控跳过】未检测到 test-coverage-analyzer skill,门控不可用。时间:<ISO>。决策人:系统(环境缺漏)。 |
| Implicit miss | 【覆盖率门控漏跑】合并已发生但门控未运行(应跑未跑)。时间:<ISO>。漏跑阶段:<合并前/合并后>。 |
Part R — PR code review (strong dependency)
Prevents merge-without-PR-review: coverage/CI can be green while the diff still carries high-confidence defects that a multi-perspective, dual-axis review would catch.
1. Resolve pr-review-gate preference
Scan AGENTS.md then CLAUDE.md (first match wins) for:
pr-review-gate:\s*(always|never|ask|non-code-light)\b
| Value | Behavior |
|---|
| (unset) | Treat as always (full-depth review — preserves prior default) |
always | Run pr-code-review at depth=full |
never | Skip pr-code-review; write project-preference 留痕; proceed to Part D |
ask | Ask the user: full / light / skip for this merge; MUST NOT auto-pick; skip needs user-explicit skip 留痕 |
non-code-light | Classify the PR surface (§2); non-application-code → depth=light; application-code → depth=full |
2. Classify PR surface (when needed)
When preference is non-code-light (or the user chose light under ask), classify the open PR/MR three-dot changed paths using the allow/deny table in reference.md「Non-application-code surface」.
- All paths allowlisted and none denylisted → non-application-code
- Any denylisted path (or mixed) → application-code
3. Run or skip
- Confirm frontmatter dependency
pr-code-review is available when a review run is required (prerequisite check already ran at load).
- If preference is
never (or ask + user skip): write 留痕 → Part D (do not load pr-code-review).
- Otherwise load
pr-code-review with the selected depth (full or light) against the open PR/MR about to be merged (follow that skill — Standards∥Spec, confidence ≥80).
- Decision matrix:
| Result | Action |
|---|
| Pass — neither axis retains ≥80 Critical/Important | Proceed to Part D |
| Fail — either axis has ≥80 Critical/Important | Block merge. Fix on the source tip, re-enter from Part A, or user explicit skip with 留痕 |
| Skill ineligible skip (closed/draft/already reviewed this session) | Treat as pass for this Part only if the PR is still the merge candidate and a prior ≥80-clean dual-axis review exists on this tip; otherwise pause for user |
留痕
| Case | Template |
|---|
| User explicit skip | 【PR code-review 门控跳过】用户显式跳过 Part R(pr-code-review)。时间:<ISO>。决策人:用户。PR:<url or id>。 |
| Project preference never | 【PR code-review 门控跳过】工程偏好 pr-review-gate: never。时间:<ISO>。决策人:项目配置。 |
| Light path used | Optional note in the review comment: pr-review-gate: non-code-light; surface=non-application-code; depth=light |
Red flags
- Skipping Part R because “CI is green” or “coverage-gate never” (coverage skip is not a Part R skip)
- Treating unset
pr-review-gate as light or never (unset MUST be always)
- Calling Claude Code
/code-review plugin as a substitute without loading pr-code-review
- Collapsing Standards and Spec into one ranked list and treating “overall look fine” as Part R pass
- Using light depth on a mixed/application-code surface under
non-code-light
Part D — Tip pinning (after Part R passes, before merge)
Prevents the stale-tip merge race: archive/fix commits pushed seconds before merge fail to enter the target because the merge fast-forwards to the pre-push tip (whose pipeline was already green), while the freshly-pushed commits stay on the source branch. (Postmortem: docs/mr-merge-stale-tip-archive-miss-incident.md.)
-
Pin the merge revision.
MERGE_SHA=$(git rev-parse origin/<source-branch>)
Merge with glab mr merge <id> --sha "$MERGE_SHA" -y (GitLab) or gh pr merge <id> --match-head-commit "$MERGE_SHA" (GitHub; older docs may say --sha — use the flag your gh supports). A bare merge with no tip pin is forbidden.
If the platform CLI has no tip-pin flag: wait for that tip's pipeline to pass before merging, and treat step 3 as the mandatory backstop.
-
Do not trust an instant Pipeline succeeded.
If a new commit was pushed just before merge, an immediately-appearing Pipeline succeeded is almost certainly the old tip's result. Verify the result's sha equals the just-pushed tip, or rely on step 1's tip pin.
-
Ancestor check after merge (mandatory).
git fetch origin <target>
git merge-base --is-ancestor "$MERGE_SHA" origin/<target> && echo OK || echo MISSING
MISSING → freshly-pushed commits (archive / specs sync / fixes) did not enter the target. Do not claim completion, do not proceed to Jira writeback. Open a backfill MR (cherry-pick) or pause for the user.
-
Dual strategy & fallback.
- Strategy A (default): MR is open and mergeable; any associated OpenSpec change is already archived (or there is no association) → merge implementation + archive on the same tip with tip pinned (run steps 1-3).
- Strategy B (recovery only): implementation MR was already merged accidentally and archive is still pending → open a separate docs MR for archive; list explicitly "archive pending !N" with 留痕; never pretend archive is already on the target. MUST NOT be recommended while the MR is still open and associated with an active change — that case is Part A block, not Strategy B.
Mode lifecycle
Asking the user under ask, or running the analyzer after opt-in, does not by itself trigger "auto reverts to manual" (sub-step of the merge flow). Gate pause (Part A block / below-threshold / crash / should-run implicit miss / wait for ask answer / Part R fail) = merge flow interrupted, reverts to manual per existing rules. Part B rebase execution follows the same rule: a user-confirmed rebase is a sub-step of the merge flow (does not itself revert to manual); an unresolved conflict or aborted rebase interrupts the merge flow and reverts to manual.
Integration guide (for referencing workflows)
- Keep in your own body: your stage ordering line (e.g.
archive → branch-closeout → merge-discipline(A→B→C→R→D) → merge → writeback), a one-line pointer to this skill, and 1-2 key red-flags. Do not copy the Part steps inline.
- Delegate to this skill: all five Parts — A (archive gate), B (rebase), C (coverage), R (PR code review via
pr-code-review), D (tip pinning).
- Pre-merge checklist (single source): reference.md「合并前检查清单」/ "Pre-merge checklist". Referencing workflows keep one pointer sentence only — do not paste the checklist into workflow
reference.md.
- Missing strong deps: abort and print per-skill
npx skills add FuDesign2008/open-skills -g --skill <name> --yes (see Prerequisite skill check).