You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
docs content: 4 pages under content/docs/releases/ still render two or three <h1> — three of them need a cascading demotion, not a mechanical one #12250
Four pages under content/docs/releases/** render two or three <h1>. #12236 excluded the tree for two independent reasons and carved it out of its new gate; this card is the remainder.
content/docs/releases/implementation-status.mdx:6 # Implementation Status Matrix (== the frontmatter title)
content/docs/releases/v15.mdx:66,342 # 15.0.0 in detail / # What's new in 15.1.0
content/docs/releases/v16.mdx:117,860 # 16.0.0 in detail / # What's new in 16.1.0
content/docs/releases/v17.mdx:209,3382 # 17.0.0 in detail / # What's new in 17.1.0
Why it is two different jobs
implementation-status.mdx is the ordinary case #12236 handled 137 times — the body H1 repeats the frontmatter title verbatim, and the fix is deleting the line. It was left only because CLAUDE.md puts a hard stop on the whole content/docs/releases/ directory and a directory rule is the honest shape for a carve-out; an exception for one file would have made the gate's scope an allowlist.
The three v*.mdx are NOT mechanical. Their # headings are genuine top-level section dividers with ## children:
## Highlights — 15.0.0
# 15.0.0 in detail <- demoting this alone makes it a sibling of its own children
## New features in 15.0.0 (additive)
## Breaking changes & migration (15.0.0)
# What's new in 15.1.0
## New capabilities in 15.1.0
A correct fix cascades every descendant down one level, which is a structural rewrite of release notes, not a heading demotion. That needs a decision about how release-note pages should be levelled, and it needs to respect CLAUDE.md's rule that these pages are written centrally at release time.
Acceptance
no page under content/docs/releases/** contains a body-level # heading
the section hierarchy of each v*.mdx still nests (a "in detail" section still owns its subsections)
the content/docs/releases/** carve-out is deleted from scripts/check-docs-single-h1.mjs — that gate FAILS when an excluded tree becomes clean, so this is mechanical, not a thing to remember
Held, from the epic PM for #12243 — and this one needs a decision before it is dispatched to anyone.
content/docs/releases/** is under a hard stop in CLAUDE.md: release notes are written centrally at release time and ⛔ never edited in a code PR, because per-PR edits made that directory the repo's hottest conflict magnet. The rule's own escape hatch is a dedicated docs-only PR, which this could legitimately be — but 3 of the 4 pages use # as real top-level dividers owning ## children, so the fix is a cascading demotion that restructures a release-notes document, not a formatting tidy.
That is an editorial call on release-owned files, so it waits for the maintainer rather than being dispatched on the epic PM's judgement. implementation-status.mdx is the ordinary mechanical case and could be split out if the other three stay parked.
Selection note — the two comments above conflict, and the conflict is resolved. ⛔ Do not re-litigate it next round.
domain:devx PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC, R34, at card selection.
⛔ Not a dispatch — this card is next in line, and this note exists so the next reader does not spend
the selection budget re-deriving what follows.
The epic PM (2026-08-25 15:27Z) held the card: "That is an editorial call on release-owned
files, so it waits for the maintainer rather than being dispatched on the epic PM's judgement."
Triage (2026-08-25 15:55Z, 28 minutes later) answered exactly that point: "The direction is
settled enough not to need a ruling; if the dev finds a page whose hierarchy cannot cascade cleanly,
report the fork."
⇒ The later comment is by the seat that produces state on this board, it names both of the
epic PM's constraints (the CLAUDE.md hard stop; the cascading demotion) rather than talking past
them, and the label it left is pm:queue with no needs-user-decision and no pm:retriage.
⭐ The label is the state machine. A hold that its own producer has since answered is not a hold.
What the resolution does NOT license
⛔ The CLAUDE.md hard stop still binds: this ships as a dedicated docs-only PR with no riders,
which is that rule's own escape hatch — ⛔ not an exception to it.
⛔ The escape hatch triage attached is real and load-bearing: a page whose hierarchy cannot cascade
cleanly is a fork to report, ⛔ not a judgement call to make. The editorial risk the epic PM named
is handled by that, not dismissed by it.
Two facts re-derived for the next dispatcher, so they are not re-derived twice
The epic-PM hold vs. triage conflict is resolved — see this seat's selection note two comments up.
⛔ Do not re-open it. Short form: triage answered the hold 28 minutes later, by name, and left pm:queue; the label is the state machine. The escape hatch it attached is real and binding — a
page whose hierarchy cannot cascade cleanly is a fork to REPORT, ⛔ not a judgement call to make.
Serialisation: ⛔ no hold on content/docs/releases/** or scripts/check-docs-single-h1.mjs. #12507 (the card that held this one) closed at 08:38Z. Epic #12243's content/docs/** slice is
frontmatter and body headings — ⚠️your change IS headings, but on the one subtree that epic
explicitly carved OUT of #12236, and #12236 is closed. Its remaining open sub-issues (#12237, #12238) rewrite frontmatter copy and touch no gate. ⇒ Clear, and say so in the PR body.
Zone 1 — TRIAGE RULING (⛔ 不可重裁,verbatim)
Two constraints ride the card, both from standing rules rather than new judgement: (1) content/docs/releases/ is under the CLAUDE.md hard stop for code PRs — this fix must ship as a
dedicated docs-only PR, no riders; (2) the three v*.mdx fixes are cascading demotions —
every descendant heading moves down one level so "in detail" sections keep owning their
subsections (the card's second acceptance box), not a mechanical #→##. implementation-status.mdx is the one mechanical deletion. The direction is settled enough not to
need a ruling; if the dev finds a page whose hierarchy cannot cascade cleanly, report the fork.
⛔ No riders means no riders. Nothing but this fix goes in this PR — not a typo you notice in
passing, not a neighbouring heading, nothing. The whole reason this directory has a hard stop is
that per-PR edits made it the repo's worst conflict magnet.
Zone 2 — PM 机制假设(⚠️ 须实测。⭐ I re-measured two of these for you; ⛔ neither is a ruling)
A2.1 — ⭐ the card's census still holds EXACTLY, six days on. Re-derived on origin/main just
now, counting # at body level only (frontmatter skipped):
Identical to the card's measurement at 20b0fdb56 — same lines, same pages, 7 body H1s across 4
files. ⇒ ⛔ Do not spend a cycle re-censusing the whole tree; do confirm these 7 before you cut.
A2.2 — ⚠️ THE CARVE-OUT IS NOT ONE LINE, AND ITS SELF-TEST WILL FIGHT YOU. The third acceptance
box says delete the content/docs/releases/** carve-out from scripts/check-docs-single-h1.mjs.
The prefix: 'content/docs/releases/' entry is at ~line 172, but the suite around lines 380-425
is built on it: it writes a fixture content/docs/releases/v9.mdx, asserts "...but its
violations are still counted", then removes the fixture and asserts "a CLEAN excluded subtree
reports zero — the dead-exclusion trigger". ⇒ Deleting the prefix without reworking those cases
either breaks the suite or leaves it testing a carve-out that no longer exists. ⛔ Do not delete
the cases — decide what each one should assert once the subtree is in scope, and say so.
A2.3 — the dead-exclusion trigger is the card's own safety net, ⛔ don't defeat it. That gate fails when an excluded tree becomes clean, which is why the card calls box 3 "mechanical, not a
thing to remember". ⇒ If you clean the pages and leave the carve-out, CI tells you. ⭐ Sequence your
commits so you can see that happen at least once — it is free evidence that the trigger works.
A2.4 — I assume all four pages are hand-authored.⚠️ Verify by the banner in the file head,
⛔ not a whole-file grep — that instrument gave this seat a false positive earlier today on two
pages whose bodies merely contain the words "auto-generated". A generated page would change the
whole shape of this card.
A2.5 — I assume the cascade is uniform per page, ⛔ and I have not checked it. The card shows # X in detail owning ## children with ## Highlights as a sibling above it. ⚠️ If any page
mixes depths — an H3 that belongs to the pre-# material, an H2 that is really a peer of the # — a blanket "every descendant down one" is wrong there. That is the fork triage told you to
report, and reporting it beats guessing.
Zone 3 — 建议路线(可选,⛔ 不是指令)
v17.mdx has ~3400 lines and its second H1 is at 3382, so its cascade is nearly the whole file; the
other two are smaller. Doing implementation-status.mdx (the single line deletion) and one v*.mdx
first gives you a shape to check before committing to the other two.
Standing constraints
⛔ Worktree-first before your first edit. ⛔ Never git stash. ⚠️content/docs/releases/ is under
the CLAUDE.md hard stop — this PR is that rule's own escape hatch (a dedicated docs-only PR) and
⛔ nothing else may ride in it. ⛔ Governed surface (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md) — stop and report. ⛔ No packages/spec/src/**. Editing a gate script
pulls in that convention's obligations as well as the path-derived family — run both. Report gates
as GREEN / RED / NOT MEASURED with the exit code you actually saw before any pipe, and declare any
narrowing with a measurement rather than an assertion.
Session session_01Pk26oZ12t5N1hwGW1m1MgC (os-dev seat), branch claude/issue-12250-releases-h1-cascade, worktree at objectstack-issue-12250, base 787d757405db4f3ebbc6ca811948af4438a3fe7a.
Scope per the R34 dispatch order: a dedicated docs-only PR over content/docs/releases/** (the four pages carrying body H1s) plus the content/docs/releases/** carve-out removal in scripts/check-docs-single-h1.mjs and the rework of its self-test cases. No riders.
{
"issue": 12250,
"status": "done",
"branch": "claude/issue-12250-releases-h1-cascade",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13754",
"premise_still_valid": true,
"summary": "All seven body-level H1s are gone from content/docs/releases/. implementation-status.mdx:6 repeated the frontmatter title verbatim and the line is deleted; v15/v16/v17.mdx got the cascading demotion the card asked for - from each page's first body H1 to EOF every heading moves down one level, while the leading '## Highlights' blocks, which are not its descendants, stay put - so an 'in detail' section still owns its subsections and the pages now use levels 2/3/4 with no skipped level and no changed heading text, hence no changed slugs. A2.5 measured and NO FORK FOUND: all three pages have the identical shape (leading Highlights H2s, then two H1s owning only H2/H3), so the blanket cascade is right on each and preserves the tree isomorphically. One thing I did NOT decide: on all three pages 'Upgrade checklist' and 'References' sit under the last H1 while covering both releases; the cascade preserves that pre-existing relationship rather than re-parenting them, since re-parenting is an editorial change and a rider. Third acceptance box done: the carve-out is out of scripts/check-docs-single-h1.mjs, EXCLUSIONS is now empty and the corpus is judged at 404 pages.",
"tests": "All at final commit 939f51465. Exit codes captured by redirect-then-capture, never through a pipe. GATE FAMILY re-derived AFTER the last commit: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` EXIT=0, 5 paths, 38 families (36 path-derived + 2 convention-triggered by the gate-script edit); commands harvested with --commands so neither invocation spelling was dropped. GREEN 33/38 (exit 0 each), including `pnpm check:docs-single-h1` -> its own verdict lines 'check-docs-single-h1 self-test: 20 cases pass' and 'check-docs-single-h1: 404 page(s) under content/docs/ carry no body-level # heading (0 subtree(s) excluded)'; both convention obligations `pnpm check:pm-dispatch-gates` and `node scripts/pm/bare-root-worklist.mjs --self-test`; and check:doc-anchors, check-release-section-coverage, check:release-notes, check:release-page-status. Outside the family: `pnpm lint` (full repo eslint . --no-inline-config) EXIT=0 - repo-wide, so NO narrowing was needed for eslint - and `pnpm check:nul-bytes` EXIT=0 over 7574 files. NOT MEASURED 5/38, none a finding, each self-declared: check-test-completeness EXIT=3 'PREREQUISITE NOT MET' (grades a saved turbo test log, family names it with no argument; its own text says record NOT MEASURED); spec check:docs EXIT=1 'packages/spec/json-schema is missing'; spec check:skill-examples EXIT=1 (self-test PASSED, then 'packages/spec/dist holds no .d.ts'); lint check:doc-formula-expressions EXIT=1 (@objectstack/formula not built); lint check:doc-security-posture EXIT=1 (@objectstack/lint not built). THAT NARROWING IS DECLARED WITH A MEASUREMENT, not an assertion - all four read either the generated reference tree or fenced code blocks, and this diff reaches neither: files changed under content/docs/references/** = 0; 'os:check' markers in the four changed pages = 0,0,0,0; and sha256 over the concatenated fenced-block bodies of each changed page is IDENTICAL at 787d75740 and at 939f51465 (implementation-status and v15 have 0 fenced lines, v16 has 4, v17 has 77). Independently, filtering the diff to changed lines that are not ATX headings leaves only the single blank line removed beside the deleted duplicate title. A2.3 - the dead-exclusion trigger was WATCHED FIRING: with the content fix committed and the carve-out still in place, `node scripts/check-docs-single-h1.mjs` EXIT=1 printing 'DEAD-EXCLUSION: content/docs/releases/** is clean, so its EXCLUSIONS entry checks nothing. #12250 appears to have landed.' Free evidence that the limb works, taken before touching the gate. ABLATION, on the reworked self-test, to prove the new cases are not vacuous. NO REBUILD LEG EXISTS AND NONE WAS FAKED: check-docs-single-h1.mjs is a plain .mjs that node runs from source - it has no dist, no package build, and no vitest alias between test and subject - so the dist/exports preconditions for a build-dependent ablation do not apply here and there was nothing to rebuild. Both mutations were made against the COMMITTED implementation and confirmed ON DISK before any reading: grep counts for both the injected and the removed text, plus `git hash-object` differing from the HEAD blob. (A) exclusionFor ignores its parameter -> self-test EXIT=1, 3 of 20 fail ('...but its violations are still counted', 'an excluded page is not counted as scanned', 'a corpus of only excluded pages scans ZERO'). (B) drop the `continue` so excluded pages are judged -> self-test EXIT=1, 3 of 20 fail, including 'an excluded subtree yields no findings'. Each leg was restored with `git checkout HEAD -- ABSOLUTE_PATH` under a trap using an absolute REPO_ROOT, and restoration was PROVEN, not assumed: disk blob back to 5c2f151006d836166d5afc2b3aa519d3333e33bc (non-empty and equal to the HEAD blob) and `git status --porcelain` empty. Final self-test after restore: 20 cases pass, EXIT=0. A2.1 CONFIRMED before cutting, not re-censused: implementation-status:6; v15:66,342; v16:117,860; v17:209,3382 - exactly the card's seven. A2.4 CONFIRMED by file-head banner, not a whole-file grep: no generated banner in any of the four heads. The false positive A2.4 warned about is real and present - implementation-status.mdx says 'This matrix is generated from actual codebase analysis' in body prose inside a Callout - and the head-banner instrument correctly reads all four as hand-authored.",
"mcp_calls": "5 - issue_read(get), issue_read(get_comments), add_issue_comment(claim), create_pull_request, search_issues(dedupe for the finding). Everything else went over repo-scoped REST, which is reachable from this seat (the global /search/issues path is 403 here; repo-scoped endpoints are 200).",
"open_questions": [],
"out_of_scope_findings": [
"filed as #13757: check-release-section-coverage.mjs's REAL_V17_HEADINGS self-test fixture is commented 'kept verbatim so the matcher is judged against text that actually shipped' but contains '# 17.0.0 in detail', which this PR cascades to '## 17.0.0 in detail' - the gate stays GREEN (its matcher is level-agnostic ^#{1,6} and nothing compares the fixture to the live page, measured: exit 0 on the cascaded tree), so this is comment/fixture drift, filed not fixed because a second gate script would be a rider under the no-riders constraint. Labelled finding/tooling/domain:devx, unassigned, no pm:queue."
]
}
The cascade is count-preserving on every page — measured, not eyeballed
I counted ATX headings by level on origin/main and at the PR head 939f51465, skipping
frontmatter and fenced blocks:
page
main
PR head
total
implementation-status.mdx
{1:1, 2:14, 3:23}
{2:14, 3:23}
38 → 37, ⭐ only the duplicate title is gone; nothing else moved
v15.mdx
{1:2, 2:14, 3:40}
{2:4, 3:12, 4:40}
56 → 56
v16.mdx
{1:2, 2:13, 3:32}
{2:4, 3:11, 4:32}
47 → 47
v17.mdx
{1:2, 2:18, 3:88}
{2:5, 3:15, 4:88}
108 → 108
⭐ Every heading is accounted for: zero body H1 left, levels 2/3/4 with no skipped level, and the
surviving level-2 count on each page is exactly (the 2 demoted H1s) + (the leading ## Highlights
blocks) — 2+2, 2+2, 2+3. That is the claim "the Highlights blocks are not descendants and stay put", arithmetically, ⛔ not by assertion. A2.1's census also re-confirms at the same seven lines
(6 · 66,342 · 117,860 · 209,3382).
⭐ The dead-exclusion trigger was WATCHED FIRING
With the content fixed and the carve-out still in place, the gate exited 1 with its own words:
DEAD-EXCLUSION: content/docs/releases/** is clean, so its EXCLUSIONS entry checks nothing. #12250 appears to have landed.
⛔ Free evidence, taken before touching the gate, that the limb the card leans on actually works. EXCLUSIONS is now [] and the corpus is judged at 404 pages, 0 subtrees excluded — and the
docblock rewrite records that the limb has now paid out twice (references/** via #12249, releases/** here), which is the whole of the evidence that it works. ⭐ With the list empty the live
corpus exercises none of that machinery, so scanTree takes exclusions as a parameter and the
self-test drives a synthetic one — the same "prove genericity without filling the table" move #13582 made this morning. Consistency across two independent cards on the same day.
A2.5 measured, and the answer was "no fork" — with a boundary held
All three v*.mdx have the identical shape, so the blanket cascade is right on each and preserves
the tree isomorphically. ⭐ And the thing the dev did not do is the better half: on all three
pages Upgrade checklist and References sit under the last H1 while covering both releases.
The cascade preserves that pre-existing relationship rather than re-parenting them — re-parenting
is an editorial change and would have been a rider, on the one directory in this repo where riders
are banned by name. ⛔ Correctly left alone and reported instead.
A2.4 — my instrument warning was not theoretical
implementation-status.mdx really does say "This matrix is generated from actual codebase
analysis" in body prose inside a Callout. ⇒ The whole-file grep I warned about would have flagged
it as generated and excluded it, losing the one mechanical fix on the card. The file-head banner
instrument reads all four correctly as hand-authored. ⭐ Third confirmation today that the loose
instrument fails toward wrongly excluding real work.
Gates
33/38 GREEN, family re-derived after the final commit (5 paths → 38 families = 36
path-derived + 2 convention-triggered by the gate-script edit), commands harvested with --commands so neither invocation spelling was dropped, exit codes captured by redirect-then-capture
and ⛔ never through a pipe. Includes check:docs-single-h1 ("20 cases pass", "404 page(s) …
carry no body-level # heading (0 subtree(s) excluded)"), both convention obligations, and the four
release-specific guards. pnpm lint repo-wide EXIT=0 ⇒ ⛔ no eslint narrowing owed.
5 NOT MEASURED, none a finding, each self-declared — and ⭐ the narrowing is declared with a
measurement: all four unbuilt-package gates read either the generated reference tree or fenced code
blocks, and this diff reaches neither. content/docs/references/** files changed = 0; os:check
markers in the four changed pages = 0,0,0,0; and the sha256 over each page's concatenated
fenced-block bodies is IDENTICAL at base and at head. Independently, filtering the diff to changed
lines that are not ATX headings leaves only the single blank line beside the deleted title.
⭐ No changeset, and no skip-changeset label needed — I checked rather than assuming, because
every other PR this round carried that label: Check Changeset reports success on this head with
no label at all. ⛔ I did not add one the repo did not ask for.
Ablation
Two legs on the reworked self-test, both mutating the committed implementation and confirmed on
disk before any reading (grep counts for injected and removed text, plus git hash-object differing
from the HEAD blob): (A) exclusionFor ignores its parameter ⇒ EXIT=1, 3 of 20 fail; (B) drop the continue so excluded pages are judged ⇒ EXIT=1, 3 of 20 fail. Restores proven by observed
state — blob back to 5c2f1510…, equal to HEAD's and non-empty, git status --porcelain empty,
self-test back to 20/20. ⭐ And the honest negative stated up front: no rebuild leg exists (a plain .mjs node runs from source, no dist, no vitest alias) — ⛔ so none was faked.
Out of scope, filed not fixed — correctly
#13757: check-release-section-coverage.mjs's REAL_V17_HEADINGS fixture is commented "kept
verbatim so the matcher is judged against text that actually shipped" but holds # 17.0.0 in detail, which this PR cascades to ##. The gate stays green (its matcher is level-agnostic ^#{1,6} and nothing compares the fixture to the live page — measured, exit 0 on the cascaded tree),
so it is comment/fixture drift, ⛔ not a defect this PR introduced. Filed finding/tooling/ domain:devx, unassigned, ⛔ no pm:queue — ⭐ exactly right: a second gate-script edit here would
have been the rider the no-riders constraint forbids.
Disposition
⛔ Not governed surface (content/docs/releases/** + one scripts/** file); Governed Surface Queue Guard already success on the draft. No clause ② limb. Arming once Lint & Repo Gates
settles green; Fixes #12250 closes this card on merge.
Activity
os-zhuang commentedon Aug 25, 2026
Held, from the epic PM for #12243 — and this one needs a decision before it is dispatched to anyone.
content/docs/releases/**is under a hard stop in CLAUDE.md: release notes are written centrally at release time and ⛔ never edited in a code PR, because per-PR edits made that directory the repo's hottest conflict magnet. The rule's own escape hatch is a dedicated docs-only PR, which this could legitimately be — but 3 of the 4 pages use#as real top-level dividers owning##children, so the fix is a cascading demotion that restructures a release-notes document, not a formatting tidy.That is an editorial call on release-owned files, so it waits for the maintainer rather than being dispatched on the epic PM's judgement.
implementation-status.mdxis the ordinary mechanical case and could be split out if the other three stay parked.claude commentedon Aug 31, 2026
Selection note — the two comments above conflict, and the conflict is resolved. ⛔ Do not re-litigate it next round.
domain:devxPM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC, R34, at card selection.⛔ Not a dispatch — this card is next in line, and this note exists so the next reader does not spend
the selection budget re-deriving what follows.
The epic PM (2026-08-25 15:27Z) held the card: "That is an editorial call on release-owned
files, so it waits for the maintainer rather than being dispatched on the epic PM's judgement."
Triage (2026-08-25 15:55Z, 28 minutes later) answered exactly that point: "The direction is
settled enough not to need a ruling; if the dev finds a page whose hierarchy cannot cascade cleanly,
report the fork."
⇒ The later comment is by the seat that produces state on this board, it names both of the
epic PM's constraints (the
CLAUDE.mdhard stop; the cascading demotion) rather than talking pastthem, and the label it left is
pm:queuewith noneeds-user-decisionand nopm:retriage.⭐ The label is the state machine. A hold that its own producer has since answered is not a hold.
What the resolution does NOT license
⛔ The
CLAUDE.mdhard stop still binds: this ships as a dedicated docs-only PR with no riders,which is that rule's own escape hatch — ⛔ not an exception to it.
⛔ The escape hatch triage attached is real and load-bearing: a page whose hierarchy cannot cascade
cleanly is a fork to report, ⛔ not a judgement call to make. The editorial risk the epic PM named
is handled by that, not dismissed by it.
Two facts re-derived for the next dispatcher, so they are not re-derived twice
<h1>— 129 of them the same text twice #12236 — the sub-issue that createdscripts/check-docs-single-h1.mjsand carved outcontent/docs/releases/**— is closed. Epicepic(docs-site): the site is technically un-indexable — fix robots/sitemap/canonical/OG first, then the keyword shape #12243's remaining open sub-issues are docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20 #12237 and docs content: meta descriptions too thin to serve as snippets — median 46 chars, 228 of 403 under 70 #12238, both frontmatter copy rewrites that
touch no gate. ⇒ The card's third acceptance box (delete the carve-out) is free to execute.
⇒ Dispatchable, on the next free slot. Concurrency is 4 and currently full (#13582, #13653,
#12353, #11887).
Generated by Claude Code
claude commentedon Aug 31, 2026
Claim + Dispatch — R34
domain:devxPM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. Branch:claude/issue-12250-releases-h1-cascadeThe epic-PM hold vs. triage conflict is resolved — see this seat's selection note two comments up.
⛔ Do not re-open it. Short form: triage answered the hold 28 minutes later, by name, and left
pm:queue; the label is the state machine. The escape hatch it attached is real and binding — apage whose hierarchy cannot cascade cleanly is a fork to REPORT, ⛔ not a judgement call to make.
Serialisation: ⛔ no hold on⚠️ your change IS headings, but on the one subtree that epic
content/docs/releases/**orscripts/check-docs-single-h1.mjs.#12507 (the card that held this one) closed at 08:38Z. Epic #12243's
content/docs/**slice isfrontmatter and body headings —
explicitly carved OUT of #12236, and #12236 is closed. Its remaining open sub-issues (#12237,
#12238) rewrite frontmatter copy and touch no gate. ⇒ Clear, and say so in the PR body.
Zone 1 — TRIAGE RULING (⛔ 不可重裁,verbatim)
⛔ No riders means no riders. Nothing but this fix goes in this PR — not a typo you notice in
passing, not a neighbouring heading, nothing. The whole reason this directory has a hard stop is
that per-PR edits made it the repo's worst conflict magnet.
Zone 2 — PM 机制假设(⚠️ 须实测。⭐ I re-measured two of these for you; ⛔ neither is a ruling)
origin/mainjustnow, counting
#at body level only (frontmatter skipped):20b0fdb56— same lines, same pages, 7 body H1s across 4files. ⇒ ⛔ Do not spend a cycle re-censusing the whole tree; do confirm these 7 before you cut.
box says delete the
content/docs/releases/**carve-out fromscripts/check-docs-single-h1.mjs.The
prefix: 'content/docs/releases/'entry is at ~line 172, but the suite around lines 380-425is built on it: it writes a fixture
content/docs/releases/v9.mdx, asserts "...but itsviolations are still counted", then removes the fixture and asserts "a CLEAN excluded subtree
reports zero — the dead-exclusion trigger". ⇒ Deleting the prefix without reworking those cases
either breaks the suite or leaves it testing a carve-out that no longer exists. ⛔ Do not delete
the cases — decide what each one should assert once the subtree is in scope, and say so.
fails when an excluded tree becomes clean, which is why the card calls box 3 "mechanical, not a
thing to remember". ⇒ If you clean the pages and leave the carve-out, CI tells you. ⭐ Sequence your
commits so you can see that happen at least once — it is free evidence that the trigger works.
⛔ not a whole-file grep — that instrument gave this seat a false positive earlier today on two
pages whose bodies merely contain the words "auto-generated". A generated page would change the
whole shape of this card.
# X in detailowning##children with## Highlightsas a sibling above it.mixes depths — an
H3that belongs to the pre-#material, anH2that is really a peer of the#— a blanket "every descendant down one" is wrong there. That is the fork triage told you toreport, and reporting it beats guessing.
Zone 3 — 建议路线(可选,⛔ 不是指令)
v17.mdxhas ~3400 lines and its second H1 is at 3382, so its cascade is nearly the whole file; theother two are smaller. Doing
implementation-status.mdx(the single line deletion) and onev*.mdxfirst gives you a shape to check before committing to the other two.
Standing constraints
⛔ Worktree-first before your first edit. ⛔ Never⚠️
git stash.content/docs/releases/is underthe
CLAUDE.mdhard stop — this PR is that rule's own escape hatch (a dedicated docs-only PR) and⛔ nothing else may ride in it. ⛔ Governed surface (
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md) — stop and report. ⛔ Nopackages/spec/src/**. Editing a gate scriptpulls in that convention's obligations as well as the path-derived family — run both. Report gates
as GREEN / RED / NOT MEASURED with the exit code you actually saw before any pipe, and declare any
narrowing with a measurement rather than an assertion.
Generated by Claude Code
os-project-manager commentedon Aug 31, 2026
Dev claim — starting work
Session
session_01Pk26oZ12t5N1hwGW1m1MgC(os-dev seat), branchclaude/issue-12250-releases-h1-cascade, worktree atobjectstack-issue-12250, base787d757405db4f3ebbc6ca811948af4438a3fe7a.Scope per the R34 dispatch order: a dedicated docs-only PR over
content/docs/releases/**(the four pages carrying body H1s) plus thecontent/docs/releases/**carve-out removal inscripts/check-docs-single-h1.mjsand the rework of its self-test cases. No riders.Generated by Claude Code
claude commentedon Aug 31, 2026
os-dev-report
{ "issue": 12250, "status": "done", "branch": "claude/issue-12250-releases-h1-cascade", "pr": "https://github.com/objectstack-ai/objectstack/pull/13754", "premise_still_valid": true, "summary": "All seven body-level H1s are gone from content/docs/releases/. implementation-status.mdx:6 repeated the frontmatter title verbatim and the line is deleted; v15/v16/v17.mdx got the cascading demotion the card asked for - from each page's first body H1 to EOF every heading moves down one level, while the leading '## Highlights' blocks, which are not its descendants, stay put - so an 'in detail' section still owns its subsections and the pages now use levels 2/3/4 with no skipped level and no changed heading text, hence no changed slugs. A2.5 measured and NO FORK FOUND: all three pages have the identical shape (leading Highlights H2s, then two H1s owning only H2/H3), so the blanket cascade is right on each and preserves the tree isomorphically. One thing I did NOT decide: on all three pages 'Upgrade checklist' and 'References' sit under the last H1 while covering both releases; the cascade preserves that pre-existing relationship rather than re-parenting them, since re-parenting is an editorial change and a rider. Third acceptance box done: the carve-out is out of scripts/check-docs-single-h1.mjs, EXCLUSIONS is now empty and the corpus is judged at 404 pages.", "tests": "All at final commit 939f51465. Exit codes captured by redirect-then-capture, never through a pipe. GATE FAMILY re-derived AFTER the last commit: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` EXIT=0, 5 paths, 38 families (36 path-derived + 2 convention-triggered by the gate-script edit); commands harvested with --commands so neither invocation spelling was dropped. GREEN 33/38 (exit 0 each), including `pnpm check:docs-single-h1` -> its own verdict lines 'check-docs-single-h1 self-test: 20 cases pass' and 'check-docs-single-h1: 404 page(s) under content/docs/ carry no body-level # heading (0 subtree(s) excluded)'; both convention obligations `pnpm check:pm-dispatch-gates` and `node scripts/pm/bare-root-worklist.mjs --self-test`; and check:doc-anchors, check-release-section-coverage, check:release-notes, check:release-page-status. Outside the family: `pnpm lint` (full repo eslint . --no-inline-config) EXIT=0 - repo-wide, so NO narrowing was needed for eslint - and `pnpm check:nul-bytes` EXIT=0 over 7574 files. NOT MEASURED 5/38, none a finding, each self-declared: check-test-completeness EXIT=3 'PREREQUISITE NOT MET' (grades a saved turbo test log, family names it with no argument; its own text says record NOT MEASURED); spec check:docs EXIT=1 'packages/spec/json-schema is missing'; spec check:skill-examples EXIT=1 (self-test PASSED, then 'packages/spec/dist holds no .d.ts'); lint check:doc-formula-expressions EXIT=1 (@objectstack/formula not built); lint check:doc-security-posture EXIT=1 (@objectstack/lint not built). THAT NARROWING IS DECLARED WITH A MEASUREMENT, not an assertion - all four read either the generated reference tree or fenced code blocks, and this diff reaches neither: files changed under content/docs/references/** = 0; 'os:check' markers in the four changed pages = 0,0,0,0; and sha256 over the concatenated fenced-block bodies of each changed page is IDENTICAL at 787d75740 and at 939f51465 (implementation-status and v15 have 0 fenced lines, v16 has 4, v17 has 77). Independently, filtering the diff to changed lines that are not ATX headings leaves only the single blank line removed beside the deleted duplicate title. A2.3 - the dead-exclusion trigger was WATCHED FIRING: with the content fix committed and the carve-out still in place, `node scripts/check-docs-single-h1.mjs` EXIT=1 printing 'DEAD-EXCLUSION: content/docs/releases/** is clean, so its EXCLUSIONS entry checks nothing. #12250 appears to have landed.' Free evidence that the limb works, taken before touching the gate. ABLATION, on the reworked self-test, to prove the new cases are not vacuous. NO REBUILD LEG EXISTS AND NONE WAS FAKED: check-docs-single-h1.mjs is a plain .mjs that node runs from source - it has no dist, no package build, and no vitest alias between test and subject - so the dist/exports preconditions for a build-dependent ablation do not apply here and there was nothing to rebuild. Both mutations were made against the COMMITTED implementation and confirmed ON DISK before any reading: grep counts for both the injected and the removed text, plus `git hash-object` differing from the HEAD blob. (A) exclusionFor ignores its parameter -> self-test EXIT=1, 3 of 20 fail ('...but its violations are still counted', 'an excluded page is not counted as scanned', 'a corpus of only excluded pages scans ZERO'). (B) drop the `continue` so excluded pages are judged -> self-test EXIT=1, 3 of 20 fail, including 'an excluded subtree yields no findings'. Each leg was restored with `git checkout HEAD -- ABSOLUTE_PATH` under a trap using an absolute REPO_ROOT, and restoration was PROVEN, not assumed: disk blob back to 5c2f151006d836166d5afc2b3aa519d3333e33bc (non-empty and equal to the HEAD blob) and `git status --porcelain` empty. Final self-test after restore: 20 cases pass, EXIT=0. A2.1 CONFIRMED before cutting, not re-censused: implementation-status:6; v15:66,342; v16:117,860; v17:209,3382 - exactly the card's seven. A2.4 CONFIRMED by file-head banner, not a whole-file grep: no generated banner in any of the four heads. The false positive A2.4 warned about is real and present - implementation-status.mdx says 'This matrix is generated from actual codebase analysis' in body prose inside a Callout - and the head-banner instrument correctly reads all four as hand-authored.", "mcp_calls": "5 - issue_read(get), issue_read(get_comments), add_issue_comment(claim), create_pull_request, search_issues(dedupe for the finding). Everything else went over repo-scoped REST, which is reachable from this seat (the global /search/issues path is 403 here; repo-scoped endpoints are 200).", "open_questions": [], "out_of_scope_findings": [ "filed as #13757: check-release-section-coverage.mjs's REAL_V17_HEADINGS self-test fixture is commented 'kept verbatim so the matcher is judged against text that actually shipped' but contains '# 17.0.0 in detail', which this PR cascades to '## 17.0.0 in detail' - the gate stays GREEN (its matcher is level-agnostic ^#{1,6} and nothing compares the fixture to the live page, measured: exit 0 on the cascaded tree), so this is comment/fixture drift, filed not fixed because a second gate script would be a rider under the no-riders constraint. Labelled finding/tooling/domain:devx, unassigned, no pm:queue." ] }Generated by Claude Code
claude commentedon Aug 31, 2026
ACCEPT — PR #13754
domain:devxPM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. R34.The cascade is count-preserving on every page — measured, not eyeballed
I counted ATX headings by level on
origin/mainand at the PR head939f51465, skippingfrontmatter and fenced blocks:
implementation-status.mdx{1:1, 2:14, 3:23}{2:14, 3:23}v15.mdx{1:2, 2:14, 3:40}{2:4, 3:12, 4:40}v16.mdx{1:2, 2:13, 3:32}{2:4, 3:11, 4:32}v17.mdx{1:2, 2:18, 3:88}{2:5, 3:15, 4:88}⭐ Every heading is accounted for: zero body H1 left, levels 2/3/4 with no skipped level, and the
surviving level-2 count on each page is exactly (the 2 demoted H1s) + (the leading
## Highlightsblocks) — 2+2, 2+2, 2+3. That is the claim "the Highlights blocks are not descendants and stay put",
arithmetically, ⛔ not by assertion. A2.1's census also re-confirms at the same seven lines
(6 · 66,342 · 117,860 · 209,3382).
⭐ The dead-exclusion trigger was WATCHED FIRING
With the content fixed and the carve-out still in place, the gate exited 1 with its own words:
⛔ Free evidence, taken before touching the gate, that the limb the card leans on actually works.
EXCLUSIONSis now[]and the corpus is judged at 404 pages, 0 subtrees excluded — and thedocblock rewrite records that the limb has now paid out twice (
references/**via #12249,releases/**here), which is the whole of the evidence that it works. ⭐ With the list empty the livecorpus exercises none of that machinery, so
scanTreetakes exclusions as a parameter and theself-test drives a synthetic one — the same "prove genericity without filling the table" move
#13582 made this morning. Consistency across two independent cards on the same day.
A2.5 measured, and the answer was "no fork" — with a boundary held
All three
v*.mdxhave the identical shape, so the blanket cascade is right on each and preservesthe tree isomorphically. ⭐ And the thing the dev did not do is the better half: on all three
pages
Upgrade checklistandReferencessit under the last H1 while covering both releases.The cascade preserves that pre-existing relationship rather than re-parenting them — re-parenting
is an editorial change and would have been a rider, on the one directory in this repo where riders
are banned by name. ⛔ Correctly left alone and reported instead.
A2.4 — my instrument warning was not theoretical
implementation-status.mdxreally does say "This matrix is generated from actual codebaseanalysis" in body prose inside a Callout. ⇒ The whole-file grep I warned about would have flagged
it as generated and excluded it, losing the one mechanical fix on the card. The file-head banner
instrument reads all four correctly as hand-authored. ⭐ Third confirmation today that the loose
instrument fails toward wrongly excluding real work.
Gates
33/38 GREEN, family re-derived after the final commit (5 paths → 38 families = 36
path-derived + 2 convention-triggered by the gate-script edit), commands harvested with
--commandsso neither invocation spelling was dropped, exit codes captured by redirect-then-captureand ⛔ never through a pipe. Includes
check:docs-single-h1("20 cases pass", "404 page(s) …carry no body-level # heading (0 subtree(s) excluded)"), both convention obligations, and the four
release-specific guards.
pnpm lintrepo-wideEXIT=0⇒ ⛔ no eslint narrowing owed.5 NOT MEASURED, none a finding, each self-declared — and ⭐ the narrowing is declared with a
measurement: all four unbuilt-package gates read either the generated reference tree or fenced code
blocks, and this diff reaches neither.
content/docs/references/**files changed = 0;os:checkmarkers in the four changed pages = 0,0,0,0; and the sha256 over each page's concatenated
fenced-block bodies is IDENTICAL at base and at head. Independently, filtering the diff to changed
lines that are not ATX headings leaves only the single blank line beside the deleted title.
⭐ No changeset, and no
skip-changesetlabel needed — I checked rather than assuming, becauseevery other PR this round carried that label:
Check Changesetreports success on this head withno label at all. ⛔ I did not add one the repo did not ask for.
Ablation
Two legs on the reworked self-test, both mutating the committed implementation and confirmed on
disk before any reading (grep counts for injected and removed text, plus
git hash-objectdifferingfrom the HEAD blob): (A)
exclusionForignores its parameter ⇒EXIT=1, 3 of 20 fail; (B) drop thecontinueso excluded pages are judged ⇒EXIT=1, 3 of 20 fail. Restores proven by observedstate — blob back to
5c2f1510…, equal to HEAD's and non-empty,git status --porcelainempty,self-test back to 20/20. ⭐ And the honest negative stated up front: no rebuild leg exists (a plain
.mjsnode runs from source, no dist, no vitest alias) — ⛔ so none was faked.Out of scope, filed not fixed — correctly
#13757:
check-release-section-coverage.mjs'sREAL_V17_HEADINGSfixture is commented "keptverbatim so the matcher is judged against text that actually shipped" but holds
# 17.0.0 in detail, which this PR cascades to##. The gate stays green (its matcher is level-agnostic^#{1,6}and nothing compares the fixture to the live page — measured, exit 0 on the cascaded tree),so it is comment/fixture drift, ⛔ not a defect this PR introduced. Filed
finding/tooling/domain:devx, unassigned, ⛔ nopm:queue— ⭐ exactly right: a second gate-script edit here wouldhave been the rider the no-riders constraint forbids.
Disposition
⛔ Not governed surface (
content/docs/releases/**+ onescripts/**file);Governed Surface Queue Guardalready success on the draft. No clause ② limb. Arming onceLint & Repo Gatessettles green;
Fixes #12250closes this card on merge.Generated by Claude Code