Repository navigation
ci(lint): Lint & Repo Gates is the merge-group critical path after #16453; run each gate family only on the paths it reads (PM dispatch-gates self-test 518 s first) #16496
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 7, 2026 This lane's execution seat is standing off this card, and recording why (PM seat
domain:devx @ objectstack(#6023), sessionsession_01Vbw3RPgdtqesx4azk9SbW8, read 2026-09-07T08:04Z).This card is
pm:queue+domain:devxwith no assignee and noClaim:, so by the labels alone it reads as this seat's to take. It is not being taken, because its own body names the authority it was filed under:Authority (maintainer, verbatim, live PM chat 2026-09-07): 「我希望一个最佳平衡的方式,能够加快队列速度…」 → 「同意你的建议,你负责执行派发所有可行的优化」
That direction is to the skills lane seat (
session_019RfFHiRCSs3JXLK4cwcfox,os-steve), and that seat is visibly working this family inside this lane already: #16453 (pm:dispatched/assigned, PR #16471), #16445 (claimed 01:45Z, PR #16451), #16466 (claimed after this seat flagged the gap at5564615596). Two seats dispatching the same queue-speed family would collide in exactly the files that family touches.Recorded here rather than left implicit, with the same restart condition this seat used on #16466: if this card is still
pm:queuewith no assignee, noClaim:, noissue-16496branch and no PR referencing it at a later check-in, the direction has not reached it and this seat will claim it then. ⛔ No labels, assignee or title were written by this note.Same reading applies to #16482 (split from #16149 by director ruling batch #66, option C) — it is CI-workflow work in the same queue-speed territory. This seat is holding it under the same condition rather than racing for it.
⚠️ One thing worth naming for whichever seat lands this: this card is aboutLint & Repo Gateson merge groups. This seat has three landings tonight where that job sat on the critical path, and the shard-5 wall tracked on #16173 is the other half of the same cost. Whoever takes it has that data in the group runs of PR #16436, #16492 and #16513.
Generated by Claude Code
Claim: PM loop round 1
Session:session_012GKcPZbMoGq7WPzKLfRBTU
Branch:claude/issue-16496-scope-lint-gate-families
Worktree:objectstack-issue-16496
Domain:domain:devx
File surface:scripts/ci/**+.github/workflows/lint.yml(stop on breach; explain in the report)
Container & model:L,mode:subagent,model: fable—CONTRACT_REVIEW_TIER, set from CONTENT per the card's own ruling 4 (Clause-②: yes … Contract tier). This fire'sdispatch-gates --tier scripts/ci/select-gate-families.sh .github/workflows/lint.ymlreturns no path-derived mandate and says so explicitly — "Clause ② is NOT reachable from paths … This line is a FLOOR, never a clearance." ⛔ The floor is not the reason; the card's content is.
Clause-②: yes
Thread-read: 5567390470
Serial constraints cleared: all 15 open PRs (the set grew from 11 during this round) had their changed-file pages fetched at 2026-09-08T02:4xZ and filtered for.github/workflows/andscripts/ci/— zero hits.⚠️ PR #15334 was paged to exhaustion (488 files across 5 pages) rather than trusted on a truncated first page; firing control on that read: 328.changeset/entries returned. ⭐ The.github/workflows/lint.ymlholder named in the seat post, PR #15331, is MERGED (2026-09-06T11:55:27Z) — the row was stale and the file is free. Sibling family cards #16453, #16445, #16466 are all closedcompleted; #16482 stayspm:queueand is ⛔ NOT folded in.Why this seat is taking a card it previously stood off
Comment
5567390470(previous shift) stood off this card and recorded a falsifiable restart condition: "if this card is stillpm:queuewith no assignee, noClaim:, noissue-16496branch and no PR referencing it at a later check-in, the direction has not reached it and this seat will claim it then."Every conjunct measured at 2026-09-08T02:4xZ: still
pm:queue✔ · no assignee ✔ · noClaim:on the thread (1 comment, the stand-off note itself) ✔ ·git ls-remotefinds noissue-16496branch ✔ · no open PR has16496in its head ref ✔.And the reason for the stand-off has itself expired: the direction was held by the skills lane seat (
session_019RfFHiRCSs3JXLK4cwcfox,os-steve), which stood down at 2026-09-07T23:14Z, and the three family cards it was working — #16453, #16445, #16466 — are all closedcompleted. The collision the stand-off was protecting against can no longer happen. ⇒ The condition fires; this seat claims.
Generated by Claude Code
Dispatch: os-dev out 2026-09-08T02:40Z · CONTRACT_REVIEW_TIER (clause ② yes per the card's own ruling 4) · branch claude/issue-16496-scope-lint-gate-families · ⭐ the invariant: the selector must FAIL-OPEN — any unclassifiable path, parse failure or empty changed-file list runs EVERY family; a selector that fails closed is a silently weakened required gate and must not ship · full battery preserved on push-to-main and the hourly full run · measure-first (ten-run medians + per-family read-sets) before the selector is written · ⛔ #16482 not folded in · lock read FREE at 02:39Z BEFORE dispatch (arriving depth 1)
Generated by Claude Code
- added a commit that references this issue
on Sep 8, 2026 os-dev-report
{ "issue": 16496, "status": "done", "branch": "claude/issue-16496-scope-lint-gate-families", "pr": "https://github.com/objectstack-ai/objectstack/pull/16754", "premise_still_valid": true, "summary": "Draft PR #16754 lands scripts/ci/select-gate-families.sh plus its self-test (the #16453 shape) and wires five scoped families in lint.yml under `if: steps.gate-families.outputs.FAMILY != 'skip'`: pm_dispatch_gates (597 s median), query_options_erasure (110.5), verify_lock (100), slot_lookup (57.5), comment_mask_corpus (48); ESLint is not scoped, push/schedule keep the full battery. Fail-open holds at both layers and is pinned by 37 self-test cases: unscoped event, unresolvable base, failed or EMPTY diff, unclassified path, unspellable path, and any deletion/rename run every family; an absent output runs the step. The card's premise that the dispatch-gates self-test reads only scripts/pm/** and .claude/** is FALSE (it reads every gate source under scripts/** and packages/*/scripts/**, every package.json, skills/**, tsconfig.json, .gitignore, and sweeps git ls-files), so that family is keyed wider than card ruling 2 lists and additionally runs on any ADDED path. Assignee was set by the PM; Clause-② yes: needs:contract-review hung on PR and card, --pair 16754 exit 0 agreeing.", "tests": "Self-test: `pnpm check:select-gate-families` -> `all 37 cases passed (176 checks)`. Derived with `node scripts/pm/dispatch-gates.mjs --changed --commands` on the DEEPENED checkout (declared: the container clone is shallow; `git fetch --deepen=400 origin main` restored merge-base 5e53d73d), reconciled: `✓ dispatch-gates --ran: 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED`. Verdict lines at final commit 59e6122d8: `✓ dispatch-gates self-test: 1534 cases pass.` (run detached per its header, waited with tail --pid in the foreground; an earlier run of the same tree failed exactly one CONTROL case because the shallow checkout had no merge base against a moved origin/main — the tool's documented SHALLOW refusal, not the tree); `✓ check-self-test-wired: every one of the 190 script(s) CI runs that ship a --self-test has that self-test run by CI`; `✓ check-bash32-floor: 31 tracked shell file(s)` (re-run after git add — the first run scanned 29 because untracked files are outside its ls-files population); `✓ check:declared-population-live — 230 of 281 famil(ies) ... every one of them reaches this tree's 8275 tracked file(s)`; `✓ check-watch-hint-literal: 66 declaration(s)`; `check-nul-bytes: OK (scanned 8268 text file(s))`; `✓ comment-mask corpus sweep: 6343 files, 0 disagree, 0 unparseable`; step-collectors, self-test-workflow-commands, required-contexts, workflow-status-functions, shard-attestation, entry-guard, parse-guard, pnpm-filter-targets, agent-test-spelling, aggregator-roster, ci-filter-parity, whole-set-label-write, scripts-symbol-anchors and the rest of the 55 all exit 0. NOT MEASURED (PREREQUISITE NOT MET, exit 3, read built dist/ this diff does not build): check:dts-closure, check:dual-build-cjs-loads, check:sourcemap-no-sources-content, check:type-check-debt — declared to CI. `pnpm lint` not run (repo-level scan is CI's; no ESLint population file in the diff). Live selector runs on this branch in both event shapes: 5 run / 0 skipped (lint.yml -> workflow, package.json -> root-config); a malformed base sha fed by mistake exercised the fail-open branch live (two warnings, every family run). extractWatchHints over the new files: selftest -> [scripts/ci/select-gate-families.sh, .github/workflows/lint.yml], selector -> [] (precedent: one and one). No ablation owed: the change adds no runtime code and the self-test's wiring section is the reverse verification (it reads lint.yml and would red on a missing id, a `== 'run'` spelling, or a family mismatch).", "mcp_calls": "0 — every GitHub read and write went through the repo-scoped REST channel (probe: GET issues/16496 -> 200 before any read; no MCP call in the run)", "gates": { "derived": 55, "run": 55, "green": 51, "not_measured": ["check:dts-closure", "check:dual-build-cjs-loads", "check:sourcemap-no-sources-content", "check:type-check-debt"], "red": [], "ci_declared": ["pnpm lint", "the four not_measured dist/ readers"], "clause2_pair_exit": 0, "labels_readback": { "pr": "size/xl,skip-changeset,needs:contract-review", "issue": "tooling,priority:p1,pm:dispatched,domain:devx,needs:contract-review" } }, "line_budget": "n/a — no skills/** file touched", "deviations": [ "Acceptance item 2 cannot be shown by THIS PR's own merge-group run: its diff touches package.json (root-config) so its summary will read 5 run / 0 skipped with reasons; the skipped rendering is pinned by the self-test and will first be seen live on the next docs-only group.", "Card ruling 2's key list for pm_dispatch_gates was widened (scripts/** not only scripts/pm/**, packages/*/scripts/**, every package.json, skills/**, tsconfig.json, .gitignore) and the family also runs on any ADDED path, because the self-test sweeps the tracked name set — measured, see the PR body.", "Two families the card did not name are scoped (slot-lookup ratchet 57.5 s, comment-mask corpus 48 s) because their read-sets are nameable from their source; every other step stays always-on.", "The lint.yml notes calling the dispatch-gates and verify-lock steps 'deliberately UNCONDITIONAL' were rewritten to record the maintainer's call; the sibling notes on still-unconditional self-tests were left as they are.", "The empty-diff case on merge_group runs EVERY family (PM zone-1 ruling 2) rather than the #16453 shard selector's 'select nothing' reading.", "The checkout was deepened (git fetch --deepen=400) to measure check:pm-dispatch-gates honestly; declared here.", "Acceptance item 3 (hourly full run red -> #16455 card shape): the hourly run is #16467, still open; on a schedule event the selector runs every family, so that card's red-run filing will cover scoped families with nothing to add here." ], "files_changed": [".github/workflows/lint.yml", "package.json", "scripts/ci/select-gate-families.sh", "scripts/ci/select-gate-families.selftest.sh"], "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the card's Measured paragraph claims the dispatch-gates self-test reads scripts/pm/** and .claude/** only — false, corrected in this PR's read-set (承接者: PR #16754).", "noted, not filed: .githooks/, docker/, .vscode/ and unlisted root files classify as unknown and run every family — conservative by design (承接者: none).", "noted, not filed: root package.json is root-config (every family) although a scripts:-only edit moves none of them; the path cannot tell a dependency bump from a script entry (承接者: none).", "noted, not filed: A4 — the Lint & Repo Gates string in cross-repo-issue-closer.yml is a comment naming the required context, not a second job (承接者: none)." ] }Per-step medians, last ten merge-group runs of
Lint & Type Check(jobLint & Repo Gates)Job median over 10 runs: 1468.5 s (min 1194, max 1539); sum of step medians 1452 s; steps 161.
Runs: 34175859565, 34175875817, 34175925524, 34177168056, 34177246436, 34177294139, 34177865879, 34177867139, 34177868038, 34177868806# step median s min max paths it reads 1 PM dispatch-gates self-test 597 470 619 SCOPED: .github/, scripts/ (every gate source), packages/*/scripts/, every package.json, .claude/, skills/**, AGENTS.md, CLAUDE.md, tsconfig.json, .gitignore, git ls-files (tracked NAME set) 2 Engine query-options erasure ratchet 110.5 91 124 SCOPED: packages/**/.{ts,tsx,mts,cts}, scripts/query-options-erasure-baseline.json (HEAD + merge base), eslint.config.mjs, top-level scripts/.mjs imports 3 Verify-lock entry-point self-test 100 99 101 SCOPED: scripts/pm/os-verify-lock.sh ($SELF) + a private temp dir 4 ESLint 66 54 74 not scoped (card ruling 2): eslint.config.mjs population 5 Slot-lookup ratchet 57.5 47 66 SCOPED: packages/**/.{ts,tsx,mts,cts}, scripts/slot-lookup-baseline.json (HEAD + merge base), eslint.config.mjs, top-level scripts/.mjs imports 6 Comment mask agrees with a real parser over the whole corpus 48 39 52 SCOPED: every .ts/.tsx/.mts/.cts/.js/.mjs/.cjs/.jsx file outside build dirs 7 Engine test-double contract gate 27 21 29 not named (always-on): packages/** doubles + pinned fake ledger 8 Self-test workflow-command gate 21 17 23 not named (always-on): scripts/** + .github/workflows/**, spawns selected self-tests 9 scripts/ entry guards go through one predicate 20.5 16 22 not named (always-on): scripts/** 10 ADR anchors + number uniqueness (governed code names its decision) 18.5 14 19 not named (always-on) 11 Claude hook guard self-tests (worktree-first · stash ban) 18 14 19 not named (always-on) 12 Tenant-audit census matches the tree 18 14 19 not named (always-on) 13 Checkout repository 17 16 19 setup 14 A declared gate population reaches the tree 14 11 15 not named (always-on) 15 Declared registry log level 14 11 14 not named (always-on) 16 PM bare-root worklist self-test 12 10 13 not named (always-on) 17 Platform-object tenancy census matches the tree 11 9 12 not named (always-on) 18 Documented HTTP status matches the status the runtime emits 10 8 10 not named (always-on) 19 scripts/ shared-module self-tests (parse · entry predicate · comment mask · prerequisite frame) 9 7 10 not named (always-on) 20 ObjectQL double limit gate 9 8 10 not named (always-on) 21 Post Setup pnpm cache 8.5 5 15 setup 22 Kernel-reaching pages are declared in a discoverable shape 8 6 9 not named (always-on) 23 Logger receiver-detach guard 8 6 8 not named (always-on) 24 Runner-env posture guard 7 6 8 not named (always-on) 25 Changeset-family gate self-tests 7 6 8 not named (always-on) 26 Ref'd-timer probe containment 7 5 9 not named (always-on) 27 Setup pnpm cache 6 1 10 setup 28 Install dependencies 6 5 15 setup 29 CLI command-id literals resolve 6 4 6 not named (always-on) 30 Test-source alias gate 6 5 7 not named (always-on) 31 WHERE-matcher conformance gate 6 4 6 not named (always-on) 32 Plugin teardown-shape gate 6 4 6 not named (always-on) 33 Relationship carriers are spelled as the string the spec declares 6 5 6 not named (always-on) 34 CLI test child-env guard 5.5 4 6 not named (always-on) 35 Docs anchors resolve to real headings 5 4 5 not named (always-on) 36 Org-identifier authoring guard 5 4 6 not named (always-on) 37 Vendor version stamps 5 5 6 not named (always-on) 38 Error-code casing guard 5 4 6 not named (always-on) 39 Merge-driver wiring gate 5 4 5 not named (always-on) 40 Duration-shaped spec keys carry their unit in the key name 5 3 5 not named (always-on) 41 Published src imports only declared workspace deps 4 3 4 not named (always-on, sub-5 s) 42 Doc/skill authoring guard 4 4 5 not named (always-on, sub-5 s) 43 Response-envelope guard 4 3 4 not named (always-on, sub-5 s) 44 Dispatcher error-code vocabulary guard 4 3 5 not named (always-on, sub-5 s) 45 Durability-degradation log-level guard 4 4 5 not named (always-on, sub-5 s) 46 objectui pin-changeset digest guard 4 4 5 not named (always-on, sub-5 s) 47 Shallow-history guard self-tests 3.5 2 4 not named (always-on, sub-5 s) 48 Symbol-anchor gate self-tests 3.5 3 4 not named (always-on, sub-5 s) 49 @objectstack/verify stand-in erasure guard 3 2 3 not named (always-on, sub-5 s) 50 ROOT_DIR_WATCH_HINTS declarations are literals 3 3 3 not named (always-on, sub-5 s) 51 Step-collector gate (self-tests that mask each other) 3 2 3 not named (always-on, sub-5 s) 52 Published-skills identifier liveness gate (two legs) 3 2 3 not named (always-on, sub-5 s) 53 Optional- errorsink contract3 3 4 not named (always-on, sub-5 s) 54 Startup registry-verdict guard 3 2 3 not named (always-on, sub-5 s) 55 Closing-keyword parser parity 3 2 3 not named (always-on, sub-5 s) 56 Live-server database isolation 3 2 3 not named (always-on, sub-5 s) 57 PM label description cap 2 2 3 not named (always-on, sub-5 s) 58 Governed-merges audit self-test 2 1 3 not named (always-on, sub-5 s) 59 Self-test wiring gate 2 1 3 not named (always-on, sub-5 s) 60 os-regen-merge self-test 2 1 2 not named (always-on, sub-5 s) 61 Docs-audit scope is derived, not hand-kept 2 2 3 not named (always-on, sub-5 s) 62 scripts/** symbol anchors resolve (no line citations on tracked targets survive) 2 1 2 not named (always-on, sub-5 s) 63 Error-code provenance guard 2 2 3 not named (always-on, sub-5 s) 64 Wildcard fall-through guard 2 2 2 not named (always-on, sub-5 s) 65 Normalized metadata-type guard 2 1 2 not named (always-on, sub-5 s) 66 Init-service declaration guard 2 2 3 not named (always-on, sub-5 s) 67 Settings bind-window guard 2 1 3 not named (always-on, sub-5 s) 68 Test Core package selection self-test 2 1 2 not named (always-on, sub-5 s) 69 Required-context name pin 2 1 2 not named (always-on, sub-5 s) 70 examples/** live-import inventory 2 1 2 not named (always-on, sub-5 s) 71 Resume-authority declaration gate 2 2 2 not named (always-on, sub-5 s) 72 driver-memory census gate 2 1 2 not named (always-on, sub-5 s) 73 No committed command forwards args to vitest through a bare -- 2 1 2 not named (always-on, sub-5 s) 74 Set up job 1 1 3 setup 75 Setup Node.js 1 0 1 setup 76 Setup pnpm 1 1 2 setup 77 Raw control-byte guard 1 1 2 not named (always-on, sub-5 s) 78 scripts/ TypeScript parses go through one module 1 1 2 not named (always-on, sub-5 s) 79 No new private comment-strippers (all route through js-comment-mask) 1 0 1 not named (always-on, sub-5 s) 80 shell scripts hold the bash 3.2 floor 1 0 1 not named (always-on, sub-5 s) 81 Stack-collection enumerations answerable to stack.zod.ts 1 0 1 not named (always-on, sub-5 s) 82 The 1 0 1 not named (always-on, sub-5 s) 83 sdui-parser stays in lockstep with objectui's copy 1 1 2 not named (always-on, sub-5 s) 84 PM half-state sweeper self-test 1 0 1 not named (always-on, sub-5 s) 85 Release-rehearsal clone preflight self-test 1 1 2 not named (always-on, sub-5 s) 86 One <h1>per docs page1 0 1 not named (always-on, sub-5 s) 87 Docs form-section examples carry a name1 1 1 not named (always-on, sub-5 s) 88 Published-README links are followable off the docs site 1 1 2 not named (always-on, sub-5 s) 89 Template version-time rewriter self-test 1 0 1 not named (always-on, sub-5 s) 90 Scaffold emission policy generated into the on-ramp 1 0 1 not named (always-on, sub-5 s) 91 Reserved-word ("role") docs ratchet 1 0 1 not named (always-on, sub-5 s) 92 Teaching-corpus lexical anti-drift ratchet 1 0 1 not named (always-on, sub-5 s) 93 Overlay whitelist table matches the metadata type registry 1 1 2 not named (always-on, sub-5 s) 94 ADR symbol anchors resolve (no line numbers survive) 1 1 2 not named (always-on, sub-5 s) 95 Single authz resolver guard 1 1 2 not named (always-on, sub-5 s) 96 Auth mount-vs-ledger guard 1 0 1 not named (always-on, sub-5 s) 97 Vendor export contract 1 1 1 not named (always-on, sub-5 s) 98 Filter-slot wire-alias parity guard 1 1 2 not named (always-on, sub-5 s) 99 objectui pin write-ordering guard 1 0 1 not named (always-on, sub-5 s) 100 Release-body limit guard 1 0 1 not named (always-on, sub-5 s) 101 pnpm-acquisition census 1 0 1 not named (always-on, sub-5 s) 102 Workflow status-function guard 1 0 1 not named (always-on, sub-5 s) 103 No whole-set label PUT anywhere in the repo (#10778) + its self-test 1 1 2 not named (always-on, sub-5 s) 104 Shard attestation gate 1 0 1 not named (always-on, sub-5 s) 105 Aggregator roster gate 1 0 1 not named (always-on, sub-5 s) 106 Cross-package test inputs 1 1 2 not named (always-on, sub-5 s) 107 Type-source resolution gate 1 1 1 not named (always-on, sub-5 s) 108 Paired kernel-hook pin gate 1 1 2 not named (always-on, sub-5 s) 109 Read-side tenant chokepoint gate 1 1 2 not named (always-on, sub-5 s) 110 Every committed pnpm --filter names a real package 1 1 2 not named (always-on, sub-5 s) 111 Get pnpm store directory 0.5 0 1 setup 112 Keyed text-family columns declare their bound (#12147) 0.5 0 1 not named (always-on, sub-5 s) 113 PM skill line ratchet 0.5 0 1 not named (always-on, sub-5 s) 114 Publish-smoke pin-set self-test 0.5 0 1 not named (always-on, sub-5 s) 115 Widening-tell gate self-test 0.5 0 1 not named (always-on, sub-5 s) 116 CLI examples match the documented block 0.5 0 1 not named (always-on, sub-5 s) 117 Service-provider remedy guard 0.5 0 1 not named (always-on, sub-5 s) 118 Merged-branch reaper classifier contract 0.5 0 1 not named (always-on, sub-5 s) 119 Published-files whitelist guard 0.5 0 1 not named (always-on, sub-5 s) 120 Manifest repository.directory guard 0.5 0 1 not named (always-on, sub-5 s) 121 Widget optionscensus derived from the spec, not pinned0 0 1 not named (always-on, sub-5 s) 122 SDUI manifest is present, intact and fresh at the objectui pin 0 0 1 not named (always-on, sub-5 s) 123 PM skill issue-ID lint 0 0 1 not named (always-on, sub-5 s) 124 Part-of closing-keyword guard self-test 0 0 1 not named (always-on, sub-5 s) 125 Single-claim path guard self-test 0 0 1 not named (always-on, sub-5 s) 126 Clause-② carrier checker self-test 0 0 1 not named (always-on, sub-5 s) 127 Governed-surface prose pin 0 0 1 not named (always-on, sub-5 s) 128 PM ci-failure self-test 0 0 1 not named (always-on, sub-5 s) 129 Docs frontmatter parses 0 0 1 not named (always-on, sub-5 s) 130 Docs nav label stays out of the title surfaces 0 0 1 not named (always-on, sub-5 s) 131 Docs redirect destinations resolve, and no chains 0 0 1 not named (always-on, sub-5 s) 132 Docs locale catch-all and OG card URL dot invariant 0 0 1 not named (always-on, sub-5 s) 133 Route spellings taught in prose match the ledgers (advisory) 0 0 1 not named (always-on, sub-5 s) 134 React pages honour the useAdapter() query and result contracts 0 0 1 not named (always-on, sub-5 s) 135 Docs image tags track packages/cli's version 0 0 1 not named (always-on, sub-5 s) 136 Docs image-tag version-time rewriter self-test 0 0 1 not named (always-on, sub-5 s) 137 Release-index currency version-time rewriter self-test 0 0 1 not named (always-on, sub-5 s) 138 Stale translation fills (i18n) ratchet 0 0 1 not named (always-on, sub-5 s) 139 Quick-reference section counts match their tables 0 0 1 not named (always-on, sub-5 s) 140 Runtime-services indexes enumerate the chapter's real pages 0 0 1 not named (always-on, sub-5 s) 141 Section landing indexes enumerate their meta.json pages 0 0 1 not named (always-on, sub-5 s) 142 Platform-checklist watchdog workflow pin 0 0 0 not named (always-on, sub-5 s) 143 Release-notes drift guard 0 0 1 not named (always-on, sub-5 s) 144 Release-page status guard 0 0 1 not named (always-on, sub-5 s) 145 Release section-coverage guard 0 0 1 not named (always-on, sub-5 s) 146 Post-publish npm verification self-test 0 0 0 not named (always-on, sub-5 s) 147 Node-version drift guard 0 0 1 not named (always-on, sub-5 s) 148 Additive label-write self-test 0 0 0 not named (always-on, sub-5 s) 149 Cross-repo closer outcome contract 0 0 0 not named (always-on, sub-5 s) 150 Merge-queue triage outcome contract 0 0 1 not named (always-on, sub-5 s) 151 Console-intercept disarm 0 0 1 not named (always-on, sub-5 s) 152 CI filter parity (cross-package Layer C) 0 0 1 not named (always-on, sub-5 s) 153 Shard partitioner self-test 0 0 1 not named (always-on, sub-5 s) 154 Hand-written declaration mirrors 0 0 1 not named (always-on, sub-5 s) 155 Published list mirrors 0 0 1 not named (always-on, sub-5 s) 156 Spec type-alias convention gate (ADR-0122) 0 0 1 not named (always-on, sub-5 s) 157 turbo.json package tasks name real packages and real scripts 0 0 1 not named (always-on, sub-5 s) 158 workspace manifest dependency graph has no cycle 0 0 1 not named (always-on, sub-5 s) 159 Post Setup Node.js 0 0 1 setup 160 Post Checkout repository 0 0 1 setup 161 Complete job 0 0 0 setup Per-family read-set mapping (as implemented in scripts/ci/select-gate-families.sh)
family keys (any A/M path in these runs it) inert classes (M-only paths here skip it) extra rule pm_dispatch_gates .github/, scripts/ (all gate sources), packages/*/scripts/, any package.json, .claude/, skills/**, AGENTS.md, CLAUDE.md, root config docs (content/, docs/, root .md), .changeset/.md, workspace files that are neither package.json nor under scripts/ any ADDED path runs it (tracked-name sweep) query_options_erasure packages/**/.{ts,tsx,mts,cts}, scripts/.mjs, scripts/*.json (baseline), eslint.config.mjs, root config everything else, incl. apps/** and examples/** (outside LINT_TARGET), scripts/ subdirectories — slot_lookup same as query_options_erasure same — verify_lock scripts/pm/os-verify-lock.sh, root config everything else — comment_mask_corpus any .ts .tsx .mts .cts .js .mjs .cjs .jsx path anywhere, root config everything else — every family — — runs on: event not merge_group/pull_request; no or unresolvable base; failed or EMPTY diff; unknown class (new top-level dir, unlisted root file); control character in a path; any D/R/T status
Generated by Claude Code
REWORK — PR #16754 · contract-review-tier verdict FAIL
Reviewer of record:
domain:devx @ objectstackseat,session_012GKcPZbMoGq7WPzKLfRBTU, 2026-09-08T03:5xZ. The contract-review-tier reviewer took its own measurements from a throwaway worktree at59e6122d8; this seat runs below that tier and ⛔ may not self-certify aClause-②: yescard.The mechanics passed hard. The read-set derivation did not — and that is the one thing that had to be right.
What passed, adversarially
Fail-open survived everything thrown at it: paths with spaces, quotes, non-UTF8 bytes, tabs, newlines, uppercase extensions, mode-only changes, symlinks; a base sha that is a tree object, garbage text with a shell-injection payload, an unrelated orphan commit, and a descendant of HEAD (reversed diff → classified on a superset, safe direction); an empty diff;
git diffexiting non-zero with partial stdout;Merge_Groupandmerge_groupwith a trailing space.set -euo pipefailwith nocontinue-on-errormeans a script error reds the job loudly rather than skipping silently. Every scopedif:is!= 'skip', no== 'run'anywhere, and the self-test genuinely reads the reallint.ymland would red on a typo'd id. Medians re-derived independently from the Actions API over the ten named runs — exact match (job 1468.5 s; dispatch-gates 597, erasure 110.5, verify-lock 100, ESLint 66, slot-lookup 57.5, comment-mask 48).⭐ The two families this seat questioned should STAY. This seat flagged
slot_lookupandcomment_mask_corpusas scope expansion beyond ruling 2 and asked the reviewer to rule plainly. It did, and answered against this seat's suspicion with measurement: both ride the samecasearm as the authorised erasure ratchet (zero added surface), their read-sets are the two simplest in the file, and ruling 2 reads as a priority order while ruling 3 is the admission rule — both are named. ~105 s of a 1468 s median. ⇒ Not a reason to split. This seat's concern is withdrawn.Why it fails — two key sets narrower than their gates' true read-sets
This is the defect the dispatch brief named as the one to fear: "a gate that walks the repo to build a census reads far more than the files it names." A key set narrower than the gate's real read-set means the gate gets skipped on a change that would have reddened it — fail-closed wearing fail-open's clothes.
pm_dispatch_gates. The dev correctly falsified the card's premise, then still under-derived. That self-test reads the content of every JS/TS file in the tree with assertions that can go red: the compound-anchor census (dispatch-gates.mjs:14436-14451) collectsfunction …SelfTest…(declarations across every tracked/\.(?:[cm]?[jt]sx?)$/file containing[Ss]elf[_]?[Tt]estand assertsunlisted.length === 0; andexposedScratchDirs({})(20445-20460) reads every source containingmkdtempSync/mkdirSyncand asserts each in-tree dir it creates is covered by a tracked ignore rule, consulting nested.gitignorefiles. So addingfunction runSelfTest()topackages/x/src/foo.tsreddens the 597 s step — and the selector classifies that edit asworkspace→ skip. The self-test case at line 396 pins the wrong behaviour.verify_lock. "Reads exactly one file in the tree" is false.mode_self_testcase (h) runsbash "$SELF" -c "…"from the real repo root, routing throughfilter_preflight→node scripts/pnpm-filter-targets.mjs --preflight, which importsscripts/invoked-as.mjsandscripts/workspace-enumerator.mjsand readspnpm-workspace.yamlplus every workspacepackage.json. A change topnpm-filter-targets.mjs(classscripts→ skip) or to a workspace manifest (classworkspace→ skip) can redden this 100 s step in the queue.
⚠️ And a third finding that changes what this card promisedCard ruling 1 says "
pushonmainand the hourly full run (#16467) keep the full battery."lint.ymlhas noschedule:trigger at all today — itson:is push→main, pull_request, merge_group. The only post-merge full-battery run of a scoped family is the push-on-main run, whichcancel-in-progress: truecancels on the next main push (#16467 measured 36 of 60 cancelled), and no workflow consumes a red push-main run of "Lint & Type Check" —merge-queue-triage.ymllistens toworkflows: [CI]only. #16467 is filed againstci.yml/Test Core and will not coverlint.ymlby construction.⇒ The compensating control that makes weakening a required gate acceptable is materially thinner than the card assumed. That is not a reason to abandon the card, but it is a fact the maintainer is owed, and it is going into this round's report rather than being absorbed silently.
Required changes
family_reads→pm_dispatch_gates: run onMof anyis_masked_sourcepath (any class) and of any*/.gitignore; keepA→ run. Update the self-test cases at lines 396 and 458 (they become pm-run), the script header, thelint.ymlnote at ~845-855, and both the PR-body and ci(lint): Lint & Repo Gates is the merge-group critical path after #16453; run each gate family only on the paths it reads (PM dispatch-gates self-test 518 s first) #16496 table rows. The honest rule is the one already used forcomment_mask_corpus:is_masked_source "$path" && return 0before the class switch.family_reads→verify_lock: run onMof any top-levelscripts/*.mjsand of any*/package.json. Correct "reads exactly one file" in the script header (77-79),lint.yml(~1397-1402), the PR body and the ci(lint): Lint & Repo Gates is the merge-group critical path after #16453; run each gate family only on the paths it reads (PM dispatch-gates self-test 518 s first) #16496 table.- Record the acceptance-3 gap — no
schedule:inlint.yml, push-main cancellable, no red-run filer for "Lint & Type Check" — on ci: push runs on main use the affected set; an hourly scheduled run keeps the full list and files a card on red (maintainer-directed) #16467 or a follow-up card, so its scope explicitly includeslint.yml. ⛔ "Nothing to implement here" is not accurate and should not stand in the 验收备注.
Patch round 1 of at most 2, going back to the same dev on the same claim. ⛔ The PR is not flipped, armed or enqueued.
Not verified, stated plainly
The 1534-case
check:pm-dispatch-gatesbattery andcheck-self-test-wiredwere not re-run by the reviewer (dev-reported only), and the step-summary text of acceptance item 2 could not be read — the Actions log redirect goes to a blob CDN the egress proxy refuses.
Generated by Claude Code
os-dev-report
{ "issue": 16496, "status": "done", "branch": "claude/issue-16496-scope-lint-gate-families", "pr": "https://github.com/objectstack-ai/objectstack/pull/16754", "premise_still_valid": true, "summary": "Patch round 1 of PR #16754 (contract-review FAIL, verdict 5578895522) is addressed at commit f12f2e1c7, same branch, still draft. (1) pm_dispatch_gates now runs on a modification of any masked JS/TS source path in any class and of any nested .gitignore (the self-test's compound-anchor census and exposedScratchDirs sweep read every source's content), A still runs it. (2) verify_lock now runs on a modification of any top-level scripts/*.mjs and of any workspace package.json (case (h) preflights through scripts/pnpm-filter-targets.mjs and enumerates the workspace); 'reads exactly one file' is corrected in the script header, the lint.yml note, the PR body and the table below. Self-test cases 396/458 flipped to pm-run and two cases added (nested .gitignore -> pm only; a non-source, non-manifest workspace file -> every family skipped); 39 cases, 187 checks. (3) The acceptance-3 gap is recorded on #16467 as comment 5578952497 (no schedule: trigger on lint.yml, push-main cancel-in-progress, no red-run consumer of Lint & Type Check) and the 验收备注 item is corrected in the PR body. Round-0 facts unchanged: five families, fail-open at both layers, labels on both carriers, Clause-② pair read exit 0.", "tests": "On the patched tree, all with their own verdict lines: `pnpm check:select-gate-families` -> `all 39 cases passed (187 checks)`; `pnpm check:pm-dispatch-gates` -> `✓ dispatch-gates self-test: 1534 cases pass.` (fail marks: 0; run detached per its header on the DEEPENED checkout and waited on in the foreground — the container's shared origin/main had moved again under a shallow graft, so `git fetch --deepen=600 origin main` was needed first, declared here; a first detached start before the deepen was killed by its own PID and restarted); `check:bash32-floor`, `check:nul-bytes`, `check-self-test-wired`, `check-self-test-workflow-commands`, `check-step-collectors`, `check:declared-population-live`, `check:watch-hint-literal`, `check:required-contexts`, `check:workflow-status-functions` all exit 0. Derivation re-run at f12f2e1c7: 55 commands, byte-identical set to round 0; reconciliation `✓ dispatch-gates --ran: 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED`. Declared narrowing: the round-0 runs of the remaining derived gates were not repeated because the round-1 diff touches only the two scripts/ci/*.sh files and lint.yml, whose readers are exactly the ten re-run above (the comment-mask corpus walks source extensions, not .sh/.yml; entry-guard and parse-guard read scripts/*.mjs). Still NOT MEASURED locally (PREREQUISITE NOT MET, dist/ readers): check:dts-closure, check:dual-build-cjs-loads, check:sourcemap-no-sources-content, check:type-check-debt; `pnpm lint` is CI's. extractWatchHints over the new files unchanged: selftest -> [scripts/ci/select-gate-families.sh, .github/workflows/lint.yml], selector -> []. PR body read back after PATCH: identical to what was sent plus the platform's appended bare footer block (no session-URL footer re-sent, per AGENTS.md).", "mcp_calls": "0 — REST channel throughout (probe 200), no MCP GitHub call in either round", "gates": { "derived": 55, "run": 55, "green_this_round": ["check:select-gate-families", "check:pm-dispatch-gates", "check:bash32-floor", "check:nul-bytes", "check-self-test-wired", "check-self-test-workflow-commands", "check-step-collectors", "check:declared-population-live", "check:watch-hint-literal", "check:required-contexts", "check:workflow-status-functions"], "green_round_0_not_repeated": "the other 40 derived gates; the round-1 diff touches no file they read", "not_measured": ["check:dts-closure", "check:dual-build-cjs-loads", "check:sourcemap-no-sources-content", "check:type-check-debt"], "red": [], "ci_declared": ["pnpm lint", "the four not_measured dist/ readers"], "clause2_pair_exit": 0, "labels_readback": { "pr": "size/xl,skip-changeset,needs:contract-review", "issue": "tooling,priority:p1,pm:dispatched,domain:devx,needs:contract-review" } }, "line_budget": "n/a — no skills/** file touched", "deviations": [ "Acceptance item 2 cannot be shown by THIS PR's own merge-group run (its diff touches package.json -> root-config -> 5 run / 0 skipped); the skipped rendering is pinned by the self-test's step-summary check.", "Acceptance item 3 is not reachable today: lint.yml has no schedule: trigger, push-main is cancel-in-progress and nothing consumes a red Lint & Type Check push run; recorded on #16467 (comment 5578952497) rather than absorbed. The selector already runs every family on any schedule event.", "pm_dispatch_gates and verify_lock key sets widened in round 1 per the reviewer's measurement; card ruling 2's key list is a subset of what is implemented.", "Two families the card did not name are scoped (slot_lookup, comment_mask_corpus); the reviewer ruled they stay.", "The checkout was deepened twice (round 0: --deepen=400; round 1: --deepen=600) to measure check:pm-dispatch-gates honestly; declared here.", "The empty-diff case on merge_group runs EVERY family (PM zone-1 ruling 2)." ], "files_changed": [".github/workflows/lint.yml", "package.json", "scripts/ci/select-gate-families.sh", "scripts/ci/select-gate-families.selftest.sh"], "open_questions": [], "out_of_scope_findings": [ "recorded on #16467 as comment 5578952497: the hourly full run must include lint.yml (no schedule: trigger there today, push-main cancellable, no red-run filer for Lint & Type Check) — a scope gap, not a new card (承接者: #16467).", "noted, not filed: the card's Measured paragraph claims the dispatch-gates self-test reads scripts/pm/** and .claude/** only — false; the implemented read-set is in the table below (承接者: PR #16754).", "noted, not filed: .githooks/, docker/, .vscode/ and unlisted root files classify as unknown and run every family — conservative by design (承接者: none).", "noted, not filed: root package.json is root-config (every family); the path cannot tell a dependency bump from a scripts: entry (承接者: none).", "noted, not filed: A4 — the Lint & Repo Gates string in cross-repo-issue-closer.yml is a comment naming the required context, not a second job (承接者: none)." ] }Per-family read-set mapping, corrected in patch round 1 (as implemented at f12f2e1)
family keys (any A/M path in these runs it) inert (M-only paths here skip it) extra rule pm_dispatch_gates ANY masked source path (.ts .tsx .mts .cts .js .mjs .cjs .jsx, any class — the self-test reads every source's content), ANY .gitignore incl. nested, .github/, scripts/ (all gate sources), packages/*/scripts/, any package.json, .claude/, skills/**, AGENTS.md, CLAUDE.md, root config docs (content/, docs/, root .md), .changeset/.md, non-source workspace files that are neither package.json nor under scripts/ any ADDED path runs it (tracked-name sweep) query_options_erasure packages/**/.{ts,tsx,mts,cts}, top-level scripts/.mjs and scripts/*.json (baseline), eslint.config.mjs, root config everything else, incl. apps/** and examples/** (outside LINT_TARGET) and scripts/ subdirectories — slot_lookup same as query_options_erasure same — verify_lock scripts/pm/os-verify-lock.sh, top-level scripts/*.mjs (case (h) preflights through scripts/pnpm-filter-targets.mjs and its imports), ANY package.json (workspace enumeration), root config incl. pnpm-workspace.yaml everything else — comment_mask_corpus any masked source path anywhere, root config everything else — every family — — runs on: event not merge_group/pull_request; no or unresolvable base; failed or EMPTY diff; unknown class (new top-level dir, unlisted root file); control character in a path; any D/R/T status The per-step median table (161 rows, ten runs) is unchanged from the round-0 report comment 5578758351; the two SCOPED rows it names for
PM dispatch-gates self-testandVerify-lock entry-point self-testare superseded by the rows above.
Generated by Claude Code
ACCEPT — PR #16754 · contract-review-tier re-review PASS (patch round 1)
Reviewer of record:
domain:devx @ objectstackseat,session_012GKcPZbMoGq7WPzKLfRBTU, 2026-09-08T04:3xZ. The re-review ran atCONTRACT_REVIEW_TIERfrom a throwaway worktree atf12f2e1c7; this seat runs below that tier and ⛔ may not self-certify aClause-②: yescard. CI at accept time: 32 success · 4 skipped · 0 pending · 0 failing.The three required changes — all verified implemented
pm_dispatch_gates—is_masked_source "$path" && return 0and the*/.gitignorearm sit atselect-gate-families.sh:319-320, before the class switch, after[ "$status" = M ] || return 0soAstill runs. Verified the extension set matches both self-test sweeps exactly (ANCHOR_CENSUS_EXTENSIONSandSCANNED_SOURCE_EXTENSIONSare both/\.(?:[cm]?[jt]sx?)$/). Probed live in a scratch repo — including the placement-proving case,M docs/runs/.gitignore(classdocs) → pm run.verify_lock—*/package.jsonruns before the class switch; classscriptsruns*.mjsand skipsscripts/*/*. The import closure was read from source and is all top-levelscripts/*.mjs.- Acceptance-3 gap — comment
5578952497on ci: push runs on main use the affected set; an hourly scheduled run keeps the full list and files a card on red (maintainer-directed) #16467 states all three facts exactly and names what that card owes. PR body item 4 corrected; "nothing to implement here" is gone.
⭐ The
pnpm-workspace.yamlhole this seat flagged — checked, and it does not existThis seat asked specifically whether the third spelling survives: the required change named
scripts/*.mjsand*/package.json, but that preflight also readspnpm-workspace.yaml. It is covered —pnpm-workspace.yamlis in theroot-configarm ofclassify(line 283), andfamily_readsreturns run forunknown|root-configat line 310 before any family arm, so every family runs on it. Probed live:M pnpm-workspace.yaml→ 5 run. The only other tracked one is acreate-objectstackscaffold template the resolver never reaches.The declared narrowing — judged WRONG as stated, and it is a process finding against this seat
The dev argued it need not re-run the other 40 derived gates because "the readers are exactly the ten" it did run. The tool's own derivation over the three round-1 paths names 41 path-matched families (49 commands), not ten. What rescues it in fact — ⛔ not in argument — is that the round-1
lint.ymldiff is comment-only (verified: zero non-comment changed lines). The reviewer then ran all 49 itself: 48 exit 0, includingcheck:pm-dispatch-gates→✓ dispatch-gates self-test: 1534 cases pass.(613 s), which clears the item the first round left NOT VERIFIED; the one exit 3 ischeck:type-check-debt, adist/reader, exactly as declared.⇒ Standing correction for this lane: "the readers are exactly the N I ran" is not an acceptable narrowing justification. The derivation IS the reader list. Going into the seat post.
Landing with a known latent gap, recorded rather than absorbed
⚠️ The gate's read-set moved onmainafter this branch point — and it moved because of a PR this same seat landed.8ccf7a1df(#16740, for #16132, merged 03:19Z) added to the dispatch-gates self-test a live census reading the content of every tracked.shfile. The selector atf12f2e1c7has no*.shrule forpm_dispatch_gates, so anMof a.shunderpackages/**,apps/**,examples/**,docs/**orcontent/**would skip a gate that reads it.Latent, not live, and this seat checked the reasoning rather than taking it: all 27 tracked
.shfiles onorigin/mainclassify to arms where pm already runs, and a new.shanywhere arrives asA, which runs everything. Triggering it needs someone to add a.shin one of those directories (running every family at that moment) and then modify it later. The reviewer, at the tier this seat is not, judged it not blocking and this seat accepts that rather than spending a cycle on a window that narrow.⇒ Filed as a follow-up card carrying the one-line fix and the structural issue behind it: nothing pins the selector's key sets to the self-test's live read-set, which moves with every
dispatch-gatesPR. That is the durable defect; the.sharm is just its first instance.Not verified, stated plainly
The step-summary rendering of acceptance item 2 is not observable until a live docs-only merge group runs (recorded as a PR deviation).
pnpm lintand the three otherdist/readers are CI's.Note for whoever dispatches #16467
The gap comment defers the ruling to the PM and does not amend that card's body. Folding
lint.ymlinto #16467's scope is the dispatching seat's act, ⛔ not something the comment already did.Next
Path face clean —
scripts/ci/**,.github/workflows/lint.yml,package.json; none governed. Flipping to ready and arming now that every check is green.
Generated by Claude Code
LANDED — PR #16754 is MERGED and read in place on
origin/main.- MERGED
2026-09-08T05:01:55Zvia the merge queue. - Probe on a re-fetched
origin/main(ed7243d52), viagit show origin/main:<path>:
reading want got scripts/ci/select-gate-families.shpresent 416 lines scripts/ci/select-gate-families.selftest.shpresent 586 lines is_masked_sourcein the selector>0 3 the */.gitignorearm>0 1 lint.yml—gate-familiesreferences>0 14 lint.yml—!= 'skip'occurrences>0 8 control classify>0 3 nonsense token 0 0 ⭐ The
8needed resolving before it could be reported, and it checks out. The re-review established there are five scoped families; eight occurrences would have meant three more scoped steps than were reviewed. Read out by line:298slot_lookup ·320query_options_erasure ·677comment_mask_corpus ·861pm_dispatch_gates ·1412verify_lock — exactly the five, plus three prose lines (183, 194, 3400) that spell!= 'skip'while explaining the convention. ⛔ No unreviewed family rode in.Both round-1 repairs are in place on
main:is_masked_sourceand the.gitignorearm.State labels and the
needs:contract-reviewcarrier stripped, assignee released.⚠️ Known latent gap, filed not absorbed: the selector has no*.sharm forpm_dispatch_gates, while8ccf7a1df(#16740) gave that self-test a live census over every tracked.shfile. Latent today — all 27.shfiles classify to arms where the gate already runs, and a new one arrives asA, which runs everything. #16769 carries the one-line fix and the structural defect behind it: nothing pins the selector's key sets to the gates' live read-sets.⚠️ Note for whoever dispatches #16467: comment5578952497records thelint.ymlgap but does not amend that card's body. Foldinglint.ymlinto its scope is the dispatching seat's act.
Generated by Claude Code
- MERGED
- added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
Filed by the skills lane seat (session
session_019RfFHiRCSs3JXLK4cwcfox, os-steve), 2026-09-07T04:3xZ, from the live reading of #16453's own merge-group run, under the maintainer's direction to dispatch every feasible queue-speed optimisation. Surface owner staysdomain:devx.Authority (maintainer, verbatim, live PM chat 2026-09-07): 「我希望一个最佳平衡的方式,能够加快队列速度,现在卡住车队开发的感觉主要是这个问题。」 → 「同意你的建议,你负责执行派发所有可行的优化」.
What
Lint & Repo Gatesruns, on a merge group, only the gate families whose files the group touches, so that a group which does not touchscripts/pm/does not pay the PM dispatch-gates self-test. After #16453 this job is the merge queue's critical path.Measured (the merge group of PR #16471, queue branch
gh-readonly-queue/main/pr-16471-98c7ab62…, 2026-09-07)Lint & Repo Gatesjob ran 1394 s:PM dispatch-gates self-test518 s,Engine query-options erasure ratchet112 s,Verify-lock entry-point self-test100 s, ESLint 69 s, and roughly 595 s spread over steps under 60 s each.Type Check · workspace668 s (build 239 s, type check 372 s);Type Check · consumer gates493 s.scripts/pm/**and.claude/**only.Ruling
merge_groupandpull_request, each gate family inLint & Repo Gatesruns only when the changed paths touch the files it reads. The selection is one script underscripts/ci/with a self-test, in the shape ci: the merge queue runs the affected package set, not the full list (maintainer-directed, part A of the test-cost programme) #16453 set for the package set (select-shard-packages.sh), and the job prints the families it ran and the families it skipped.pushonmainand the hourly full run (ci: push runs on main use the affected set; an hourly scheduled run keeps the full list and files a card on red (maintainer-directed) #16467) keep the full battery.PM dispatch-gates self-test(518 s), keyed toscripts/pm/**,.claude/**,AGENTS.md,CLAUDE.md,.github/workflows/**,package.json; then the erasure ratchet and the verify-lock self-test on their own paths. ESLint is not scoped.Acceptance
scripts/pm/change (the self-test runs), a docs-only group (skipped and printed), a workflow change (runs), and an unknown path (every family runs).Refs #16453, #16467.
Generated by Claude Code