Skip to content

Commit de0464a

Browse files
os-zhuangclaude
andauthored
docs(agents),ci: release notes are release-owned, scoped re-verify, merge_group triggers (#4490)
* docs(agents),ci: release notes are release-owned, scoped re-verify, merge_group triggers Three multi-agent throughput fixes from one PR's merge history (#4458 went three full green CI cycles without landing — main merged 18 PRs in the same 6 hours, and two of its three conflicts were rows racing into the same release-notes table): 1. `content/docs/releases/` is RELEASE-OWNED — never edited in a code PR. Release notes are compiled centrally at release time from changesets + the ADR-0087 registries; a per-PR appended row made `releases/v<major>.mdx` the repo's hottest conflict magnet. Inlined in CLAUDE.md (the must-never-miss set), added to the AGENTS.md Documentation Guardrails table, and the spec-property-retirement skill's checklist item — the one instruction that explicitly sent agents into that file — now routes through the changeset instead. 2. AGENTS.md multi-agent §10 scopes the post-merge re-verify. The first pull-main-and-full-suite round stays. Subsequent merges done only because main moved during CI: rebuild + `check:generated` when spec moved on either side, assert the branch's delta vs main is still exactly the PR's intent, and reserve the full typecheck+test for semantic overlap or non-mechanical conflicts. CI validates the merge commit either way; a 15-minute full lap per merge is what turns a busy main into a livelock. 3. `merge_group:` triggers on the three required-check workflows (ci.yml, lint.yml, spec-liveness-check.yml), so the repo can turn on GitHub's merge queue — the race-free version of §10, run by the platform. ci.yml details: the paths filter has no merge_group support, so queue builds treat everything as changed (a skipped filter step's empty output falls back to 'true'), and the full-suite step runs on queue builds (the queue result IS the next main). §7 now names the queue as the sanctioned path once an admin enables it — the opposite of the auto-merge it bans, since the queue lands only speculatively-merged-and-green results. pr-automation and docs-drift-check are deliberately NOT queue-triggered (PR-context-bound; must not be marked required). Enabling the queue itself is a branch-protection setting only an admin can flip; this commit makes the workflows ready for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * chore: empty-frontmatter changeset — this PR releases nothing The Check Changeset gate requires each PR to declare its release impact; the sanctioned "releases nothing" declaration is an empty-frontmatter changeset (per the gate's own inline doc), which a docs+workflow-only PR is exactly the case for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0f9faa2 commit de0464a

7 files changed

Lines changed: 96 additions & 22 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
---
3+
4+
Releases nothing — repo process + CI only. `content/docs/releases/` becomes
5+
RELEASE-OWNED (never edited in code PRs; compiled centrally from changesets +
6+
the ADR-0087 registries), AGENTS.md multi-agent §10 scopes the post-merge
7+
re-verify, and the three required-check workflows gain `merge_group:` triggers
8+
so the merge queue can be enabled. No package ships from this change.

.claude/skills/spec-property-retirement/SKILL.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,15 @@ Work top to bottom; each line has a gate behind it.
244244
`tsc` finds these for you on the tombstone route.
245245
- [ ] **Published skills**`skills/*/SKILL.md` teaching the key (tables,
246246
`defineX` examples) — gated by `check:skill-examples` and `check:skill-refs`.
247-
- [ ] **Docs**`content/docs/**` prose, tables and code blocks. Grep the key,
248-
then read the surrounding files: a removed key hides in a `defineFlow`
249-
example three sections from the reference table.
250-
- [ ] **Release notes** — the `### Dead spec clusters removed` table in
251-
`content/docs/releases/v<major>.mdx` **plus** the upgrade checklist.
247+
- [ ] **Docs**`content/docs/**` prose, tables and code blocks — **EXCEPT
248+
`content/docs/releases/`, which a code PR must never touch** (AGENTS.md
249+
Documentation Guardrails). Release notes are written centrally at release
250+
time from the changesets + the D2/D3 registries; the per-PR row this list
251+
used to require made `releases/v<major>.mdx` the repo's hottest conflict
252+
magnet. Your changeset (next item) is the input that reaches them. For
253+
the rest of `content/docs/**`: grep the key, then read the surrounding
254+
files — a removed key hides in a `defineFlow` example three sections from
255+
the reference table.
252256
- [ ] **Changeset**`major` for `@objectstack/spec`. AGENTS.md: a breaking
253257
changeset must carry the FROM → TO mapping and the one-line fix; it ships
254258
as `CHANGELOG.md` in the npm package and is what an upgrading agent greps

.github/workflows/ci.yml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ on:
77
pull_request:
88
branches:
99
- main
10+
# Merge queue: the queue builds each PR as speculatively merged onto the
11+
# current main and only lands it if this workflow is green on that result —
12+
# the race-free version of the "pull main and re-verify before merging"
13+
# discipline (AGENTS.md multi-agent §7/§10). Every workflow that produces a
14+
# branch-protection-required check MUST carry this trigger, or queue builds
15+
# wait forever on a check that never reports.
16+
merge_group:
1017

1118
# Superseded runs on the same PR/branch waste runners and delay feedback;
1219
# cancel them. Push runs to main group by commit ref as well, so an in-flight
@@ -23,15 +30,21 @@ jobs:
2330
contents: read
2431
pull-requests: read
2532
outputs:
26-
docs: ${{ steps.changes.outputs.docs }}
27-
core: ${{ steps.changes.outputs.core }}
28-
console: ${{ steps.changes.outputs.console }}
33+
# On merge_group, everything counts as changed: dorny/paths-filter has no
34+
# merge_group support, and the queue build is the last validation before
35+
# main — the one place a skipped job can never be the right answer. A
36+
# skipped step's output is the empty string (falsy), so `|| 'true'`
37+
# supplies the merge-group value without touching PR/push behavior.
38+
docs: ${{ steps.changes.outputs.docs || 'true' }}
39+
core: ${{ steps.changes.outputs.core || 'true' }}
40+
console: ${{ steps.changes.outputs.console || 'true' }}
2941
steps:
3042
- name: Checkout repository
3143
uses: actions/checkout@v7
3244

3345
- uses: dorny/paths-filter@v4
3446
id: changes
47+
if: github.event_name != 'merge_group'
3548
with:
3649
filters: |
3750
docs:
@@ -168,9 +181,11 @@ jobs:
168181
# workflow (coverage-nightly.yml) — instrumentation added minutes to
169182
# every main push for a trend artifact that is consulted occasionally at
170183
# best. Dogfood is excluded for the same reason as the PR step: the
171-
# Dogfood job runs it.
184+
# Dogfood job runs it. Merge-queue builds take this full-run path too:
185+
# the queue result IS the next main, so it gets main's validation, not
186+
# the PR's affected-only subset.
172187
- name: Run all tests (push)
173-
if: github.event_name == 'push'
188+
if: github.event_name == 'push' || github.event_name == 'merge_group'
174189
env:
175190
NODE_OPTIONS: --report-on-signal --report-signal=SIGUSR2 --report-directory=${{ runner.temp }}/stall-reports
176191
run: |

.github/workflows/lint.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ on:
77
pull_request:
88
branches:
99
- main
10+
# Merge queue (see ci.yml for the full note): required checks must report on
11+
# queue builds or the queue stalls. This workflow has no PR-only steps, so
12+
# the trigger alone is enough.
13+
merge_group:
1014

1115
# Same policy as ci.yml: superseded runs on the same PR/branch waste runners
1216
# and delay feedback; cancel them. Push runs to main group by commit ref, so an

.github/workflows/spec-liveness-check.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ on:
2424
# Same for the strictness ledger — it is a doc, and editing it can break
2525
# the gate that now holds it to the code.
2626
- 'docs/audits/**'
27+
# Merge queue (see ci.yml for the full note). merge_group has no `paths`
28+
# support, so queue builds run this unconditionally — acceptable: the whole
29+
# job is ~a minute, and the queue result is the next main.
30+
merge_group:
2731

2832
permissions:
2933
contents: read

AGENTS.md

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ Even inside your own worktree, operate defensively:
146146
Auto-merge can land a still-red PR onto shared `main` and break it for every
147147
parallel agent (see #1475). Merge serially; rebase other open branches before
148148
merging the next one.
149+
**Once the repo's merge queue is enabled, "add to queue" IS the sanctioned
150+
path** — it is the opposite of the auto-merge this rule bans: the queue
151+
builds your PR *as merged onto the current `main`* and lands it only if that
152+
speculative result is green, which is exactly the §10 re-verification, done
153+
by the platform, race-free. The manual serial protocol above is the fallback
154+
for when the queue is unavailable. (Why this matters: `main` can land a PR
155+
every few minutes at peak; a manual merge–reverify loop takes ~25 minutes,
156+
so under load it *never* wins the race — one PR went three full green
157+
cycles without managing to land. That is a livelock, not a discipline
158+
failure.)
149159
8. **Testing needs a server? Start your own temporary one — never stop someone
150160
else's.** A running dev server you didn't start probably belongs to another
151161
agent or the user; killing it (or its port) breaks their in-flight work. Spin
@@ -174,15 +184,31 @@ Even inside your own worktree, operate defensively:
174184
None of this is CI-visible: CI checks out fresh and installs clean. It costs
175185
only *your* time, which is exactly why it is worth recognising in one step
176186
rather than re-diagnosing per gate.
177-
10. **A clean merge is not a working merge.** Git conflicts on overlapping lines;
178-
nothing warns you when two changes are individually fine and jointly wrong.
179-
Real examples from one branch's lifetime: a test asserting a response body's
180-
exact shape landed while that shape was being changed elsewhere (merged clean,
181-
failed CI); a domain file was deleted while another agent's guard still
182-
declared it. **Before opening a PR, and again before merging, pull `main` and
183-
re-run the suite** — the second CI round is where these surface, and the guards
184-
in `scripts/check-*.mjs` exist largely because this class of breakage is
185-
invisible to `git merge`.
187+
10. **A clean merge is not a working merge — but scope the re-check to the
188+
overlap.** Git conflicts on overlapping lines; nothing warns you when two
189+
changes are individually fine and jointly wrong. Real examples from one
190+
branch's lifetime: a test asserting a response body's exact shape landed
191+
while that shape was being changed elsewhere (merged clean, failed CI); a
192+
domain file was deleted while another agent's guard still declared it.
193+
**Before opening a PR, pull `main`, refresh build state (§9), and run the
194+
full suite once.** For the *subsequent* pre-merge merges of `main` — the
195+
ones you do only because `main` moved again while CI ran — the full suite is
196+
usually re-proving what three identical runs already proved, at ~15 minutes
197+
per lap while `main` lands a PR every few. Scope it instead:
198+
- **Always:** rebuild what the merge touched, and if `packages/spec` moved
199+
on either side, `pnpm --filter @objectstack/spec build && pnpm --filter
200+
@objectstack/spec check:generated` — generated snapshots (`api-surface`,
201+
baselines) are the classic jointly-wrong artifact, and only a rebuild of
202+
the merged source can validate them (never trust git's textual merge of a
203+
generated file). Then assert your branch's *delta vs `main`* is still
204+
exactly what your PR intends (e.g. "N removed / 0 added").
205+
- **Full `pnpm typecheck && pnpm test` again only when** the incoming
206+
commits touch the same packages or the same behavior your diff does, or a
207+
conflict occurred outside trivially-mechanical files.
208+
- CI on the PR (and the merge queue, once enabled) validates the merge
209+
commit itself — that second CI round is where joint breakage surfaces, and
210+
the guards in `scripts/check-*.mjs` exist largely because this class of
211+
breakage is invisible to `git merge`.
186212

187213
---
188214

@@ -255,6 +281,7 @@ Root also exports: `defineStack`, `composeStacks`, `defineView`, `defineApp`, `d
255281
| Path | Type | Rule |
256282
|:---|:---|:---|
257283
| `content/docs/references/` | **AUTO-GEN** | ❌ Never hand-edit. Regenerated by `packages/spec/scripts/build-docs.ts`. |
284+
| `content/docs/releases/` | **RELEASE-OWNED** | ❌ Never edit in a code PR. Release notes are written **centrally at release time**, compiled from changesets + the ADR-0087 registries — not accreted a row per PR. Per-PR appends made `releases/v<major>.mdx` the repo's hottest conflict magnet (three PRs raced the same table inside one afternoon), and every manual resolution risks dropping someone else's row. Your PR's input is its **changeset**; for spec removals also the D2/D3 registry entries. Factual error on a releases page → dedicated docs-only PR or an issue, never a rider on code changes. |
258285
| `**/translations/*.generated.ts` (nine packages — `platform-objects`, five plugins, three services) | **AUTO-GEN** | ❌ Never hand-edit the file *structure*. Run `node scripts/check-i18n-bundles.mjs --write` to regenerate all nine (merge mode — every existing translation is preserved); `pnpm i18n:extract` still covers `platform-objects` alone. Translation *values* are hand-written and expected to be: the gate compares against a merge-mode extract, so editing a string is fine, while adding or dropping keys is drift. `pnpm check:i18n` gates all nine in CI, and `pnpm check:i18n-coverage` ratchets untranslated declared labels. |
259286
| `content/docs/guides/` | hand-written | ✅ Update `meta.json` when adding pages. |
260287
| `content/docs/concepts/` | hand-written ||

CLAUDE.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# CLAUDE.md
22

33
**[AGENTS.md](./AGENTS.md) is the source of truth for working in this repo — read it.**
4-
Its Prime Directives are binding. Do not rely on this file alone; the two rules that must
5-
never be missed are inlined here because missing either one wastes or corrupts other
6-
agents' work.
4+
Its Prime Directives are binding. Do not rely on this file alone; the three rules that
5+
must never be missed are inlined here because missing any one of them wastes or corrupts
6+
other agents' work.
77

88
## ⛔ Claim the issue before you write any code
99

@@ -34,5 +34,17 @@ Then make all edits there. This applies **per repo**: if a task spans `framework
3434
file's own repo (so sibling repos are covered). Deliberate non-task exception:
3535
`OS_ALLOW_MAIN_EDITS=1`. Follow the rule because it's correct, not because the hook fires.
3636

37+
## ⛔ Never edit `content/docs/releases/` in a code PR
38+
39+
Release notes are written **centrally, at release time** — not accreted one PR at a
40+
time. Every code/feature/retirement PR appending its own row to the current
41+
`releases/v<major>.mdx` turns that file into the single hottest merge-conflict magnet in
42+
the repo (with ~18 merges to `main` in a working day, the same table conflicts over and
43+
over, and each resolution risks dropping someone else's row). Your PR's inputs to the
44+
release notes are the **changeset** (`.changeset/*.md` — one file per change, never
45+
conflicts) and, for spec removals, the ADR-0087 registries; the release process compiles
46+
them. If you believe a releases page has a factual error, file an issue or make it a
47+
dedicated docs-only PR — never a rider on code changes.
48+
3749
See **AGENTS.md** for the full playbook: branch hygiene, the dev stack, PR flow, and the
3850
rest of the Prime Directives.

0 commit comments

Comments
 (0)