Uh oh!
There was an error while loading. Please reload this page.
feat(devx): gate .claude/skills/** on the ADR merge-approval check - #9407
Conversation
…9395) Implements the maintainer ruling on #9319 decision 2 (2026-08-17): the `ADR maintainer approval` gate's path predicate extends to `.claude/skills/**`, so Prime Directive #14's human-merge reservation is machine-enforced on the lane's own protocol surface rather than declared. - the single ADR prefix becomes a GOVERNED_SURFACES table: identical pass conditions (an APPROVED standing + no armed auto-merge), distinct failure wording, each surface naming its own rule; - the #8012 armed-auto-merge clause reaches the new path class identically, with its own emptiness proof (identical reviews, opposite verdicts); - one path hit governs the whole PR -- a mixed diff is never judged by proportion, pinned by comparing a 1-of-4 verdict with a 1-of-1 one; - the report renderer is pure, so the "each surface names its own rule" requirement is asserted on the text an operator actually reads; - PR #9238 -- the skills-only PR the merge queue landed with zero reviews -- joins the historical replay fixtures as a real capture, pinned RED. The workflow needed no trigger change: it carries no `paths` filter (a path filter would skip the whole workflow and hang the queue on a required context that never reports), and #9238 already produced a completed `ADR maintainer approval` check run through the clean path. No existing check is weakened: every prior assertion still runs, and the self-test goes from 66 to 96 assertions. Fixes#9395
os-zhuang
commented
Aug 17, 2026
ACCEPT — PM review at Verified against the diff, not the report: two files exactly (gate script + workflow); the Both out-of-scope findings routed correctly (#9403 AGENTS.md PD #14 text, #9404 published Flipped ready + armed merge-when-ready; the in-flight ESLint (which carries the gate self-test family) and Type Check gate the landing via the required set. Not a governed-surface diff — lands through the lane. Merge closes #9395. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9395
Implements the maintainer ruling on #9319 decision 2 (2026-08-17, 「同意」 batch): the
ADR maintainer approvalgate's path predicate extends to.claude/skills/**, so Prime Directive #14's human-merge reservation is machine-enforced on the lane's own protocol surface instead of declared. The #8012 armed-auto-merge clause reaches the new path class identically.This PR is not itself skill-surface — it touches
scripts/and.github/workflows/only, so it lands through the queue normally. The gate's own run on this diff says so:✅ No files under docs/adr/** / .claude/skills/** in this diff (2 changed file(s)).What changed
scripts/check-adr-merge-approval.mjsGOVERNED_SURFACEStable:docs/adr/and.claude/skills/, identical pass conditions (an APPROVED current standing plus no armed auto-merge), distinct failure wording — each surface carries its ownrulestring, so an ADR red cites the 2026-08-12 approval ruling and ⛔ Discipline: ADRs are confirmed and merged by the maintainer only — no AI seat may merge, queue, or auto-merge adocs/adr/**PR #6741 while a skills red cites Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 and the finding: skill-surface PR #9238 (.claude/skills/**) was flipped ready, enqueued, and merged by the queue — Prime Directive #14 human-merge bypassed #9319 ruling. A table, not anif: a third surface is one entry with its own wording, and no surface can be added wearing another's words.decide()returnssurfaces(grouped, in table order) andfiles(the flat union) in place ofadrFiles; the clean-path kind isno-governed-diff. One path hit governs the whole PR — proportion is never computed, matching feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14's 「混合 diff 一条命中就分叉,⛔ 不按比例判」.renderVerdict(), printed by a thinreportVerdict(). That is what makes "each surface names its own rule" assertable on the text an operator actually reads rather than on the verdict object..claude/skills/and notskills/: the ruling names the internal prefix, and the published catalog's merge posture is a separate decision nobody has made. Stated in the header, pinned by a self-test assertion, and filed as finding: the publishedskills/catalog is human-merge by convention but is not gated — two of the pm-dispatch fork's three prefixes are enforced #9404 rather than quietly decided here.HISTORICAL_VIOLATIONSas a real capture (its two.claude/skills/pm-dispatch/**files, its genuinely empty review list, both read from the API), pinned RED forever..github/workflows/adr-merge-approval.yml— comments and the step name only. No trigger change, and no job rename (the job name is the required status context registered inscripts/check-required-contexts.mjs; renaming it would leave the ruleset waiting for a context that never reports).Trigger verification (verified, not assumed)
Two directions, both recorded in the workflow header:
paths/paths-ignorefilter exists on any of the three triggers, so path membership cannot affect whether the workflow runs. A path filter skips the whole workflow, so the check context is never created — a required context that never reports hangs the merge queue until the ruleset's 60-minute timeout (objectui#3523). Adding one for the new prefix would be a regression, not an improvement..claude/skills/**-only diff, before any widening — already produced a completedADR maintainer approvalcheck run: job95293672713, conclusionsuccess, via the zero-lookup clean path. The run existed; only the verdict on it changes.auto_merge_enabled/auto_merge_disabledare likewise path-agnostic, so clause (2) observes arming on the new surface on the same terms.Reverse verification (predicted, then observed)
Predicted directions were written down before each run; all four matched. The old script is
git show origin/main:scripts/check-adr-merge-approval.mjs, run side by side with the new one over identical inputs.Offline replay (real CLI,
--files-json/--reviews-json/--pull-json), diff =.claude/skills/pm-dispatch/SKILL.md+package.json(a mixed diff, deliberately):exit=0—No files under docs/adr/ in this diff (2 changed file(s))exit=1—1 file(s) under .claude/skills/**…Prime Directive #14exit=0—review standing is APPROVED (approved by 'hotlong') and auto-merge is OFFexit=1—AUTO-MERGE IS ARMED … enabled by 'os-zhuang'Rows 3 and 4 are the clause-(2) emptiness proof on the new path class: identical reviews, identical files, only the arming bit differs, opposite verdicts. Without it the widening would be inert on exactly the state #9238 was in.
Gate mode over a real
git diff(a throwaway detached worktree atorigin/mainwith one synthetic edit to a.claude/skills/**file, never committed, never pushed, removed afterwards):exit=0,✅ No files under docs/adr/ in this diff (1 changed file(s), git diff against merge-base with origin/main).exit=1, and the log line.claude/skills/** touched -- consulting reviews of PR #9379 (resolved via HEAD commit subject)proves the predicate matched and the gated path was entered. The local red is then the fail-loud path (HTTP 401: Bad credentials— this container has no usable GitHub token), not the approval verdict; the verdict-level demonstration is the offline replay above. Reported as observed rather than as the template's presumption.Tests
All at
cc1a065(git rev-parse --short HEADafter the final commit):pnpm check:adr-merge-approval→✓ 96 assertions(was 66 onorigin/main: +30). Every pre-existing assertion still runs — no existing check is weakened.node scripts/pm/dispatch-gates.mjs, all green:check:node-version,check:required-contexts,check:shard-attestation,check:workflow-status-functions, pluscheck:nul-bytes(any-edit) andnpx eslint scripts/check-adr-merge-approval.mjs --no-inline-config(clean).node scripts/pm/dispatch-gates.mjs --tier scripts/check-adr-merge-approval.mjs .github/workflows/adr-merge-approval.yml, verbatim:New assertions worth naming (the ones that fail if the widening is faked):
skill-prefix-matches-only-dot-claude-skillsandthe-published-skills-catalog-is-NOT-governed— the trailing slash and the catalog exclusion, both near misses pinned.skills-armed-clause-changes-a-verdict-that-would-otherwise-be-green— the emptiness proof for the new class.a-mixed-skills-diff-is-gate-scopedandproportion-changes-no-verdict— a 1-of-4 governed diff must be as red as a 1-of-1 one.a-skills-red-does-NOT-cite-the-adr-rule/an-adr-red-does-NOT-cite-the-skill-rule/a-mixed-red-carries-BOTH-rule-texts— the distinct-wording requirement, asserted on rendered text.historical-pr-9238-is-red-BECAUSE-of-the-widened-predicate— narrow the predicate back to the ADR prefix and this one fails while every other historical replay stays green.Out of scope, filed
docs/adr/**, but the gate it describes now also holds.claude/skills/**#9403 — AGENTS.md's Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 text still names onlydocs/adr/**, so the directive the gate now cites does not name the surface it holds. Rewording a Prime Directive is the maintainer's, not a rider on a gate PR.skills/catalog is human-merge by convention but is not gated — two of the pm-dispatch fork's three prefixes are enforced #9404 (finding) — the publishedskills/catalog is human-merge by pm-dispatch convention but ungated; two of that fork's three prefixes are now enforced. Deliberate here, recorded there.Changeset
None:
scripts/+.github/workflows/only, nothing published changes. Theskip-changesetlabel is applied to this PR.Generated by Claude Code