From 709c90bf86c37397a6b21542f514c199412922e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 06:58:29 +0000 Subject: [PATCH 1/2] docs(agents): Prime Directive #14 governs three surfaces, and names the register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- AGENTS.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3536241183..ef6140cee5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,11 +173,17 @@ Other scripts: `objectui:bump` (pull only), `objectui:build`, `objectui:clean`. 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- -b main && cd ../objectstack- && 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. 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. 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. -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: +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.** Two maintainer rulings, verbatim and untranslated; the second extends the first from ADRs to skills: - > **adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。** + > **adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。** (2026-08-08) - **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 machine enforcement — `docs/adr/` in CODEOWNERS plus a required check that stays red unless the maintainer's own account has approved; this directive is the part that binds the seat reading it. A seat that has read this far is not thereby licensed to judge an exception; the rule has no exception to judge. + > **所有 skills 的更新和 adr 类似,需要人工审核** (2026-08-11) + + **Which surfaces — and where that list actually lives.** Three prefixes as of 2026-08-18: `docs/adr/**`; `.claude/skills/**`, the repo-internal playbooks every *later* dispatch reads; and `skills/**`, the catalog published outward to customer projects. ⚠️ **Read that as a reading aid, not as the register.** The set grew twice in two days and no gate ties this prose to it, so a stale count here is a silent, permanent lie. The register is the `GOVERNED_SURFACES` table in `scripts/check-adr-merge-approval.mjs`, reported by the required check **`ADR maintainer approval`** — print today's set rather than trusting the sentence above, and add a fourth surface *there*, never here: `node -e "import('./scripts/check-adr-merge-approval.mjs').then(m=>console.log(m.governedGlobs().join(' · ')))"` + + **Authoring stays open to every seat** — drafting the ADR or the skill, pushing the branch, opening the PR, revising it under review. What is reserved is the **landing**: on any PR whose diff touches a governed prefix, ⛔ 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 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 two skill prefixes are reserved for a different reason of the same shape: the internal playbooks 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. + + **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 machine enforcement — and know exactly how much of it there is.** The required check `ADR maintainer approval` covers all three prefixes and stays red while the PR has no standing APPROVED review **or** has auto-merge armed (armed is red whether or not it is approved: auto-merge is a standing instruction to merge later, and the unattended merge happens in the armed-and-approved state). What it does **not** do is read actors, by ruling — maintainer, 2026-08-12, verbatim: 「不要指定具体的人」. Any account with review rights satisfies it, an AI seat included, and no check can see who presses merge. CODEOWNERS routes `docs/adr/` to the maintainer and **nothing routes the two skill prefixes**, so on those the automatic request for his review does not exist either. The machine narrows the hole; this directive is the part that closes it. A seat that has read this far is not thereby licensed to judge an exception; the rule has no exception to judge. 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: @@ -325,9 +331,11 @@ Even inside your own worktree, operate defensively: **arm only what is already green and accepted.** ⛔ **Two classes of PR never enter this path, however green:** (a) a diff touching - `docs/adr/**` (**Prime Directive #14**); (b) the **Version Packages** PR, or any PR - whose merge performs a release (**Prime Directive #15**). Read the PR's file list - (`get_files`) **and its author** before you arm anything. + any **governed surface** (**Prime Directive #14**, which names them and holds the + current list) — that list is longer than `docs/adr/**` and has grown twice in two + days, so re-read it rather than recalling it; (b) the **Version Packages** PR, or any PR whose merge performs + a release (**Prime Directive #15**). Read the PR's file list (`get_files`) **and + its author** before you arm anything. **Green means the gate-carrying jobs' `conclusion` is `success`** — not "no failure yet"; `in_progress` is not a pass. Arming a red PR does not queue it, it hides it: @@ -908,10 +916,10 @@ new open registry? Add it to `OPEN_CAPABILITY_REGISTRIES` in the same PR that fi a feature branch, commit, push, open a PR, and — once remote CI is fully green and the PR is accepted — arm auto-merge so the queue lands it (Multi-agent discipline §7: never straight to `main`; never arm a PR that isn't green yet). A finished task - = a merged PR, not a dirty working tree. ⛔ **Except a diff touching `docs/adr/**`**: - push it, open the PR, and stop there — landing it is the maintainer's, by hand - (Prime Directive #14). For that one class, a finished task = a PR left visibly - awaiting a human merge. + = a merged PR, not a dirty working tree. ⛔ **Except a diff touching a governed + surface** (Prime Directive #14 names them — more than ADRs): push it, open the PR, + and stop there, landing it is the maintainer's, by hand. For that class, a + finished task = a PR left visibly awaiting a human merge. 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. **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. **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*: From ebd2eb26882331857549d7b6f504b25da1dfbd0a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 07:00:36 +0000 Subject: [PATCH 2/2] 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 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- AGENTS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index ef6140cee5..e24f7e121f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -650,6 +650,9 @@ agent playbooks live in `.claude/skills/` and must carry `metadata.internal: tru `dogfood-verification` (boot and drive the real app in a browser) and `spec-property-retirement` (ADR-0049 enforce-or-remove — the full retirement kit). +⛔ **Both roots are governed surfaces**: a PR touching either is human-merge only and may +never be queued or armed — **Prime Directive #14**, machine-enforced, not advisory. + --- ## Patterns