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
Commit e6ee690
Browse filesBrowse the repository at this point in the historyBrowse files
docs(agents): Prime Directive #14 governs three surfaces, and names the register that holds them (#9527)
* docs(agents): Prime Directive #14 governs three surfaces, and names the register
PD#14 scoped itself to `docs/adr/**` while the required check it describes
(`ADR maintainer approval`) has held `.claude/skills/**` since 2026-08-17 and
`skills/**` since 2026-08-18. Two more sites in the same file enumerated the
same one prefix: the §7 arming exclusion and the Post-Task Checklist exception.
- PD#14 becomes a class rule ("a governed surface"), carries both maintainer
rulings verbatim with dates, names the three prefixes ONCE, and points at
`GOVERNED_SURFACES` in scripts/check-adr-merge-approval.mjs as the register —
with the runnable one-liner that prints today's set.
- §7 and the Post-Task Checklist stop enumerating and defer to #14, so the
next surface addition edits one place, not four.
- Corrects the same paragraph's stale description of the enforcement: the gate
reads state, not actors (maintainer ruling 2026-08-12), and CODEOWNERS routes
only `docs/adr/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
* docs(agents): point the Skills section at #14's human-merge reservation
A seat working on a skill lands in the Skills section, not in the Prime
Directives. It described both roots and said nothing about their merge
posture. One class pointer, no second enumeration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
---------
Co-authored-by: Claude <noreply@anthropic.com>
11. **Worktree-first — never edit on the shared `main` checkout.** This repo is edited by **multiple agents at once**; the shared tree has its HEAD switched and reset *under you*, silently clobbering uncommitted work — a feature branch on the *shared* checkout is **not** enough (it still gets switched under you). Before your **first file edit**, be in a dedicated worktree on a feature branch: `git worktree add ../objectstack-<task> -b <branch> main && cd ../objectstack-<task> && pnpm install`. Two PreToolUse hooks **enforce** this — `.claude/hooks/guard-main-checkout.sh` blocks `Edit`/`Write`/`NotebookEdit`, and `.claude/hooks/guard-main-checkout-bash.sh` blocks the identical write arriving through **Bash** (`>`/`>>` redirection, `sed -i`, `perl -i`, `tee`, `cp`, `mv`, `rm`, `touch`) — and both check the **target file's own repo**, so sibling repos (`objectui`/`cloud`) you touch are covered too (deliberate non-task override: `OS_ALLOW_MAIN_EDITS=1`, one switch for both). The Bash guard is precision-first: it never blocks reads, and any shape it cannot resolve with confidence (`bash -c …`, `xargs`, `node -e`, a `$VAR`/glob target) is allowed through — the rule still outranks the hook. **The one thing a worktree does *not* isolate is the stash** — `refs/stash` lives in the **common** `.git`, shared by every worktree; a third hook (`guard-shared-stash.sh`, `OS_ALLOW_STASH=1`) blocks the mutating forms, and the collision-free replacements are in Multi-agent discipline below.
174
174
12. **Contract-first — fix the metadata, not the runtime.** This is a metadata-driven framework: `packages/spec` is the one contract between metadata *producers* and the runtime/renderers that *consume* it. When a piece of metadata "doesn't work," ask **first**: *is it spec-compliant? is this the long-term-correct direction?* If the metadata is wrong, fix it at the **producer** and **reject it at authoring/publish** (validation / lint) so the error surfaces loudly — do **not** add a lenient alias or `??` fallback in a consumer (a node executor, the REST layer, a renderer) to tolerate off-spec input. A tolerant fallback fossilizes the wrong convention into a second de-facto contract, dilutes the spec, and hides the producer's bug — one strict contract beats N dialects. This is an **internal** contract (we own both ends), so "be liberal in what you accept" (Postel) does **not** apply — that's for untrusted boundaries. Change the **spec** only when the spec itself is genuinely wrong, and then deliberately (edit the Zod schema + migrate), never by accreting consumer-side fallbacks. When an alias must be tolerated at all, declare it as an **ADR-0087 conversion-layer entry** (never a bare `??`, and no executor shims) so it is declared, loud, tested, and *removable on a schedule* — the `cfg.filter ?? cfg.filters`-style fallbacks the flow executors once carried were all paid down exactly that way, emptying and deleting the executor shim that read them. Stored `sys_metadata` rows (data at rest) are covered from the other side: every rehydration seam replays the **full** conversion chain — retired entries included — via `applyConversionsToStoredItem` (ADR-0087 addendum), so a consumer never needs its own accommodation for a legacy stored shape either. *Worked example:* an AI-authored flow node used wrong key names and template syntax for what the executor reads → the fix was correcting the authoring skill + a publish-gate lint that rejects the wrong shape, **not** a runtime alias in the executor (that alias was proposed and rejected). Strengthens #5.
175
175
13. **An accepted ADR binds until a superseding ADR says otherwise.** Reversing a recorded decision is itself a decision: it needs a **new ADR** (or an amended status line on the old one), not a changeset that quietly does the opposite. Before changing behaviour in `docs/adr/`-governed territory, **grep the ADRs for the surface you are touching** — the decision is often older and broader than the code comment in front of you. A reversal of three accepted ADRs once landed as a patch-level changeset and held for a day; the mechanism was not carelessness — **the file being edited never named the ADRs that governed it**, so the author could not have known. Hence the corollary: when you implement an ADR's decision, **leave its id in the code**, and anchor load-bearing spots in `scripts/adr-anchors/` (`pnpm check:adr-anchors`) — **one new JSON file per anchor, named for the path it anchors; there is no index to register it in** — so the next author is told which decision they are standing on. A decision nobody can find is a decision that will be reversed.
176
-
14.**⛔ An ADR is confirmed and merged by the maintainer, by hand — no AI seat merges, queues, or arms auto-merge on a `docs/adr/**` PR.**Maintainer ruling, 2026-08-08, verbatim and untranslated:
176
+
14.**⛔ A governed surface is confirmed and merged by the maintainer, by hand — no AI seat merges, queues, or arms auto-merge on a PR whose diff touches one.**Three maintainer rulings, verbatim and untranslated — each widens the one before it, and the third *is* the current definition of the surface:
177
177
178
-
> **adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。**
178
+
> **adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。** (2026-08-08)
179
179
180
-
**Authoring stays open to every seat** — drafting an ADR, pushing the branch, opening the PR, revising it under review. What is reserved is the **landing**: on any PR whose diff touches `docs/adr/**`, ⛔ never merge it, ⛔ never add it to the merge queue, ⛔ never call `enable_pr_auto_merge`. Judge it on the PR's **file list**, not on its description, and a **mixed diff is not a proportion question** — one path hit is enough; if the rest needs to land, split the ADR into its own PR. **Reviewed + approved + fully green does not override this.** Under #13 an accepted ADR *is* the decision, so merging one is the act of adopting a governance position — the one class of change about which "CI is green" carries no information at all (a thorough, fully-green ADR draft has been closed by the maintainer on demand grounds no gate could evaluate). **Already armed or queued when you read this?** ⚠️ Converting the PR back to **draft** is the only action that reliably removes it from the merge queue; `disable_pr_auto_merge` alone drops the arming but **not** queue membership. Do both, then confirm from the remote that it is in neither the queue nor `origin/main` (§7's draft-flip re-arm note, run backwards). ⚠️ **And do not read draft as a barrier that holds by itself** — a drafted ADR PR has nevertheless been merged, twice, by two different AI seats within one hour of the ruling above (both ratified retroactively, explicitly setting no precedent). The barrier is **this directive**, and it is the only pre-merge barrier there is — the per-PR approval check that used to sit beside it retired under the maintainer's 2026-08-18 ruling that a human merge IS the review record for a governed surface (it was red on every governed PR by design, sat outside the required-context set, and never blocked anything). Behind the directive sits detection, not prevention: `docs/adr/` in CODEOWNERS routes review requests, and the report-only post-merge audit (`scripts/pm/check-governed-merges.mjs`, whose header carries this rule's incident history) lists every governed-surface merge for the PM round report — an entry the maintainer does not recognise is a seat violation, filed and rolled back. A seat that has read this far is not thereby licensed to judge an exception; the rule has no exception to judge.
**Which surfaces — and where that list actually lives.** The 2026-08-18 quotation above *is* the definition, and it is wider than ADRs and wider than skills: the two repo-root instruction files are on it, so **the file you are reading is itself a governed surface**, and so is `.claude/` entire — agents, hooks and settings, not only `.claude/skills/`. ⚠️ Even so, treat that quotation as a reading aid rather than the register: the set has grown three times in two days. The register is the `GOVERNED_SURFACES` table in `scripts/pm/check-governed-merges.mjs`, and adding a surface is an edit *there*, never here. Print today's set rather than trusting this paragraph: `node -e "import('./scripts/pm/check-governed-merges.mjs').then(m=>console.log(m.GOVERNED_SURFACES.map(s=>s.glob).join(' · ')))"`
185
+
186
+
**Authoring stays open to every seat** — drafting the ADR, the skill or the instruction edit, pushing the branch, opening the PR, revising it under review. What is reserved is the **landing**: on any PR whose diff touches a governed surface, ⛔ never merge it, ⛔ never add it to the merge queue, ⛔ never call `enable_pr_auto_merge`, ⛔ never flip it out of draft to make any of those possible. Judge it on the PR's **file list**, not on its description, and a **mixed diff is not a proportion question** — one path hit is enough; if the rest needs to land, split the governed files into their own PR. **Reviewed + approved + fully green does not override this.** Under #13 an accepted ADR *is* the decision, so merging one is the act of adopting a governance position — the one class of change about which "CI is green" carries no information at all (a thorough, fully-green ADR draft has been closed by the maintainer on demand grounds no gate could evaluate). The other surfaces are reserved for a reason of the same shape: the agent instruction tree and these two root files are the operating protocol every *later* dispatch reads, and the published catalog lands in codebases this repo cannot see, so a bad merge propagates into work nobody has started yet — and green says nothing about whether it should propagate.
187
+
188
+
**Already armed or queued when you read this?** ⚠️ Converting the PR back to **draft** is the only action that reliably removes it from the merge queue; `disable_pr_auto_merge` alone drops the arming but **not** queue membership. Do both, then confirm from the remote that it is in neither the queue nor `origin/main` (§7's draft-flip re-arm note, run backwards). ⚠️ **And do not read draft as a barrier that holds by itself** — a drafted ADR PR has nevertheless been merged, twice, by two different AI seats within one hour of the first ruling above (both ratified retroactively, explicitly setting no precedent), and a skill PR whose own body said it was awaiting a human merge was flipped ready by an unidentified seat and landed by the merge queue with zero reviews of any kind. **The barrier is this directive, and it is the only pre-merge barrier there is.** The per-PR approval check that used to sit beside it retired under the maintainer's 2026-08-18 ruling that a human merge IS the review record for a governed surface — 「人工合并即人工审核」 — because it was red on every governed PR by design, sat outside the required-context set, and so never blocked anything. ⛔ Do not read that retirement as a relaxation: it removed a check that was not holding, and left the discipline carrying the whole load. Behind the directive sits **detection, not prevention**: `docs/adr/` in CODEOWNERS routes review requests — and it is the *only* governed surface routed there, so on the other four nothing summons the maintainer automatically — while the report-only post-merge audit (`scripts/pm/check-governed-merges.mjs`, whose header carries this rule's incident history) lists every governed-surface merge for the PM round report. Every entry on that list should be a merge the maintainer performed or ordered in person; one he does not recognise is a seat violation, filed and rolled back. A seat that has read this far is not thereby licensed to judge an exception; the rule has no exception to judge.
181
189
182
190
15.**⛔ A version release is performed by the maintainer, by hand — no AI seat publishes, tags, cuts a Release, or triggers a release workflow, and none merges the Version Packages PR.** Maintainer ruling, 2026-08-07, verbatim and untranslated:
183
191
@@ -325,9 +333,12 @@ Even inside your own worktree, operate defensively:
325
333
**arm only what is already green and accepted.**
326
334
327
335
⛔ **Two classes of PR never enter this path, however green:** (a) a diff touching
328
-
`docs/adr/**` (**Prime Directive #14**); (b) the **Version Packages** PR, or any PR
329
-
whose merge performs a release (**Prime Directive #15**). Read the PR's file list
330
-
(`get_files`) **and its author** before you arm anything.
336
+
any **governed surface** (**Prime Directive #14**, which names them and holds the
337
+
current list) — that list is much longer than `docs/adr/**`, it grew three times in
338
+
two days, and **this file and `CLAUDE.md` are on it**, so re-read it rather than
339
+
recalling it; (b) the **Version Packages** PR, or any PR whose merge performs a
340
+
release (**Prime Directive #15**). Read the PR's file list (`get_files`) **and its
341
+
author** before you arm anything.
331
342
332
343
**Green means the gate-carrying jobs' `conclusion` is `success`** — not "no failure
333
344
yet"; `in_progress` is not a pass. Arming a red PR does not queue it, it hides it:
@@ -642,6 +653,11 @@ agent playbooks live in `.claude/skills/` and must carry `metadata.internal: tru
642
653
`dogfood-verification` (boot and drive the real app in a browser) and
643
654
`spec-property-retirement` (ADR-0049 enforce-or-remove — the full retirement kit).
644
655
656
+
⛔ **Both roots are governed surfaces**: a PR touching either is human-merge only and may
657
+
never be queued, armed or flipped out of draft — **Prime Directive #14**. No per-PR check
658
+
holds this any more; the discipline is the whole barrier, and a post-merge audit lists
659
+
what landed.
660
+
645
661
---
646
662
647
663
## Patterns
@@ -908,10 +924,10 @@ new open registry? Add it to `OPEN_CAPABILITY_REGISTRIES` in the same PR that fi
908
924
a feature branch, commit, push, open a PR, and — once remote CI is fully green and
909
925
the PR is accepted — arm auto-merge so the queue lands it (Multi-agent discipline
910
926
§7: never straight to `main`; never arm a PR that isn't green yet). A finished task
911
-
= a merged PR, not a dirty working tree. ⛔ **Except a diff touching `docs/adr/**`**:
912
-
push it, open the PR, and stop there — landing it is the maintainer's, by hand
913
-
(Prime Directive #14). For that one class, a finished task = a PR left visibly
914
-
awaiting a human merge.
927
+
= a merged PR, not a dirty working tree. ⛔ **Except a diff touching a governed
928
+
surface** (Prime Directive #14 names them — more than ADRs): push it, open the PR,
929
+
and stop there, landing it is the maintainer's, by hand. For that class, a
930
+
finished task = a PR left visibly awaiting a human merge.
915
931
3.**Add a changeset for feature work.** When the change is a feature or functional improvement, run `pnpm changeset` (or add a `.changeset/*.md` entry) describing it before committing. Pure bug fixes do **not** require a changeset.
916
932
**Breaking changesets must carry their migration.** If the change removes or renames anything an author can write (a spec key, an export, a config field), the changeset body must state the FROM → TO mapping and the one-line fix — this text ships to consumers as `CHANGELOG.md` inside the npm package and is what an upgrading agent greps after the tombstone error. Removing an authorable spec key also requires a tombstone so the rejection itself carries the prescription — `retiredKey()` (`packages/spec/src/shared/retired-key.ts`) on a non-strict schema, or an entry in the relevant `UNKNOWN_KEY_GUIDANCE` / `*_RETIRED_KEY_GUIDANCE` map (see `object.zod.ts`, `ai/tool.zod.ts`) when the schema is `.strict()`. The changeset is one of fourteen surfaces a retirement touches — follow the `spec-property-retirement` skill (`.claude/skills/`) rather than reconstructing the kit, and note the two routes imply **opposite** liveness-ledger dispositions.
917
933
**A breaking changeset must also state its ADR-0087 disposition, in writing.** Add exactly one marker to the changeset body — `pnpm check:adr-0087-registration` enforces it, and the CI step is *Require an ADR-0087 disposition on a declared-breaking changeset*:
0 commit comments