Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/agents-releases-freeze-merge-queue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
---

Releases nothing — repo process + CI only. `content/docs/releases/` becomes
RELEASE-OWNED (never edited in code PRs; compiled centrally from changesets +
the ADR-0087 registries), AGENTS.md multi-agent §10 scopes the post-merge
re-verify, and the three required-check workflows gain `merge_group:` triggers
so the merge queue can be enabled. No package ships from this change.
14 changes: 9 additions & 5 deletions .claude/skills/spec-property-retirement/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,15 @@ Work top to bottom; each line has a gate behind it.
`tsc` finds these for you on the tombstone route.
- [ ] **Published skills** — `skills/*/SKILL.md` teaching the key (tables,
`defineX` examples) — gated by `check:skill-examples` and `check:skill-refs`.
- [ ] **Docs** — `content/docs/**` prose, tables and code blocks. Grep the key,
then read the surrounding files: a removed key hides in a `defineFlow`
example three sections from the reference table.
- [ ] **Release notes** — the `### Dead spec clusters removed` table in
`content/docs/releases/v<major>.mdx` **plus** the upgrade checklist.
- [ ] **Docs** — `content/docs/**` prose, tables and code blocks — **EXCEPT
`content/docs/releases/`, which a code PR must never touch** (AGENTS.md
Documentation Guardrails). Release notes are written centrally at release
time from the changesets + the D2/D3 registries; the per-PR row this list
used to require made `releases/v<major>.mdx` the repo's hottest conflict
magnet. Your changeset (next item) is the input that reaches them. For
the rest of `content/docs/**`: grep the key, then read the surrounding
files — a removed key hides in a `defineFlow` example three sections from
the reference table.
- [ ] **Changeset** — `major` for `@objectstack/spec`. AGENTS.md: a breaking
changeset must carry the FROM → TO mapping and the one-line fix; it ships
as `CHANGELOG.md` in the npm package and is what an upgrading agent greps
Expand Down
25 changes: 20 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ on:
pull_request:
branches:
- main
# Merge queue: the queue builds each PR as speculatively merged onto the
# current main and only lands it if this workflow is green on that result —
# the race-free version of the "pull main and re-verify before merging"
# discipline (AGENTS.md multi-agent §7/§10). Every workflow that produces a
# branch-protection-required check MUST carry this trigger, or queue builds
# wait forever on a check that never reports.
merge_group:

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

- uses: dorny/paths-filter@v4
id: changes
if: github.event_name != 'merge_group'
with:
filters: |
docs:
Expand Down Expand Up @@ -168,9 +181,11 @@ jobs:
# workflow (coverage-nightly.yml) — instrumentation added minutes to
# every main push for a trend artifact that is consulted occasionally at
# best. Dogfood is excluded for the same reason as the PR step: the
# Dogfood job runs it.
# Dogfood job runs it. Merge-queue builds take this full-run path too:
# the queue result IS the next main, so it gets main's validation, not
# the PR's affected-only subset.
- name: Run all tests (push)
if: github.event_name == 'push'
if: github.event_name == 'push' || github.event_name == 'merge_group'
env:
NODE_OPTIONS: --report-on-signal --report-signal=SIGUSR2 --report-directory=${{ runner.temp }}/stall-reports
run: |
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
pull_request:
branches:
- main
# Merge queue (see ci.yml for the full note): required checks must report on
# queue builds or the queue stalls. This workflow has no PR-only steps, so
# the trigger alone is enough.
merge_group:

# Same policy as ci.yml: superseded runs on the same PR/branch waste runners
# and delay feedback; cancel them. Push runs to main group by commit ref, so an
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/spec-liveness-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ on:
# Same for the strictness ledger — it is a doc, and editing it can break
# the gate that now holds it to the code.
- 'docs/audits/**'
# Merge queue (see ci.yml for the full note). merge_group has no `paths`
# support, so queue builds run this unconditionally — acceptable: the whole
# job is ~a minute, and the queue result is the next main.
merge_group:

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

---

Expand Down Expand Up @@ -255,6 +281,7 @@ Root also exports: `defineStack`, `composeStacks`, `defineView`, `defineApp`, `d
| Path | Type | Rule |
|:---|:---|:---|
| `content/docs/references/` | **AUTO-GEN** | ❌ Never hand-edit. Regenerated by `packages/spec/scripts/build-docs.ts`. |
| `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. |
| `**/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. |
| `content/docs/guides/` | hand-written | ✅ Update `meta.json` when adding pages. |
| `content/docs/concepts/` | hand-written | ✅ |
Expand Down
18 changes: 15 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# CLAUDE.md

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

## ⛔ Claim the issue before you write any code

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

## ⛔ Never edit `content/docs/releases/` in a code PR

Release notes are written **centrally, at release time** — not accreted one PR at a
time. Every code/feature/retirement PR appending its own row to the current
`releases/v<major>.mdx` turns that file into the single hottest merge-conflict magnet in
the repo (with ~18 merges to `main` in a working day, the same table conflicts over and
over, and each resolution risks dropping someone else's row). Your PR's inputs to the
release notes are the **changeset** (`.changeset/*.md` — one file per change, never
conflicts) and, for spec removals, the ADR-0087 registries; the release process compiles
them. If you believe a releases page has a factual error, file an issue or make it a
dedicated docs-only PR — never a rider on code changes.

See **AGENTS.md** for the full playbook: branch hygiene, the dev stack, PR flow, and the
rest of the Prime Directives.
Loading