This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new 41bba7a  docs(setup-steward): always-on families + reload + worktree 
chain (#184)
41bba7a is described below

commit 41bba7a7a0303377963504ce7df4474d15421e8e
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat May 16 14:54:06 2026 +0200

    docs(setup-steward): always-on families + reload + worktree chain (#184)
    
    Adds five behaviours to /setup-steward: always-on setup-* / list-steward-*
    families (no opt-out), in-flight skill reload after self-update,
    hook+config sync from the snapshot, family-wide symlink refresh on every
    upgrade, and automatic worktree-init on every linked worktree as the
    final pass of adopt and upgrade. Documentation-only — no executable
    code paths in this repo.
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
 .claude/skills/setup-steward/SKILL.md         |  61 +++++-
 .claude/skills/setup-steward/adopt.md         | 229 ++++++++++++++++++---
 .claude/skills/setup-steward/upgrade.md       | 276 +++++++++++++++++++-------
 .claude/skills/setup-steward/verify.md        |  74 +++++--
 .claude/skills/setup-steward/worktree-init.md |  54 ++++-
 5 files changed, 577 insertions(+), 117 deletions(-)

diff --git a/.claude/skills/setup-steward/SKILL.md 
b/.claude/skills/setup-steward/SKILL.md
index 5b0b927..b5b1ac9 100644
--- a/.claude/skills/setup-steward/SKILL.md
+++ b/.claude/skills/setup-steward/SKILL.md
@@ -236,6 +236,52 @@ patch tool. See
 [`docs/setup/agentic-overrides.md`](../../../docs/setup/agentic-overrides.md)
 for the contract.
 
+**Golden rule 8 — two families are *always* installed; the
+rest are opt-in.** Two skill families are wired up
+unconditionally on every adopt / upgrade / worktree-init run
+and the user is **never asked** about them:
+
+- **`setup-*`** — every framework skill whose name starts
+  with `setup-` *except* `setup-steward` itself (which is
+  copied per Rule 6, not symlinked). Concretely:
+  `setup-isolated-setup-install`,
+  `setup-isolated-setup-update`,
+  `setup-isolated-setup-verify`, `setup-override-upstream`,
+  `setup-shared-config-sync`, plus any new `setup-*` skill
+  the framework grows in the future.
+- **`list-steward-*`** — every framework skill whose name
+  starts with `list-steward-`. Today this is
+  `list-steward-skills` only; the prefix lets the framework
+  grow a discovery family without re-prompting every
+  adopter.
+
+These two families are not exposed in the `skill-families:`
+prompt and not stored as user-selectable in the lock files;
+every sub-action that wires symlinks always covers them in
+addition to the user's opt-in family picks (`security`,
+`pr-management`). Dropping them is *not* a supported
+configuration — the secure-setup and discovery flows the
+framework ships depend on those skills being callable.
+
+**Golden rule 9 — reload `setup-steward` in-flight after a
+self-update.** When a sub-action changes or creates the
+content of the committed `setup-steward` skill (in practice:
+`adopt` recovering an out-of-date bootstrap, or `upgrade`'s
+overwrite-from-snapshot step), the agent **re-reads the
+modified files of this skill before continuing** the rest of
+the current run. Concretely: after the copy lands on disk,
+re-load `SKILL.md` and the sub-action file you are
+currently executing (and any helper file you have already
+opened, such as `conventions.md` or `overrides.md`), then
+resume from the step after the overwrite. The reload runs as
+the **first thing** that happens after the overwrite, before
+any further reconciliation, symlink work, or doc updates.
+The reason: the snapshot's skill version may have renamed
+steps, added new sub-actions, or changed the symlink
+contract; finishing the run against the *old* in-memory
+copy of the skill would silently mis-apply the new
+framework version the project just pinned to.
+
 ## Sub-actions
 
 The skill dispatches by the first positional argument:
@@ -259,6 +305,19 @@ automatically sees the refreshed snapshot once the main 
runs
 upgrade, because each worktree's `<snapshot-dir>` is a symlink to
 the main's.
 
+**`adopt` and `upgrade` always chain into `worktree-init` on every
+linked worktree as their final pass.** The chain is unconditional
+— even on a fresh adoption with no linked worktrees yet (the pass
+becomes a no-op), even on an upgrade where every worktree already
+looks wired (`worktree-init` is idempotent, repairs broken
+symlinks, and adds new always-on-family entries the upgrade
+introduced). The user does not need to remember to `cd` into each
+worktree and re-run anything; the main-checkout sub-action
+propagates state outward to the worktrees by itself. See
+[`adopt.md` Step 
12.2](adopt.md#step-12--post-install-sync--worktree-propagation--sanity-check)
+and
+[`upgrade.md` Step 
6c](upgrade.md#step-6c--propagate-to-every-worktree-run-worktree-init-unconditionally).
+
 If the snapshot is missing (no `<snapshot-dir>/`) and
 `<committed-lock>` exists, the skill treats any sub-action as
 the recover-snapshot path: re-install per the committed lock
@@ -270,7 +329,7 @@ first, then continue.
 |---|---|
 | `from:<git-ref>` / `from:<version>` | Adopt or upgrade from a specific 
framework ref or version. Used during `adopt` (overrides the user prompt) and 
`upgrade` (overrides the committed lock for *this run only* — does NOT update 
the committed lock). |
 | `method:<git-branch\|git-tag\|svn-zip>` | Pick the install method 
explicitly. Default during `adopt`: prompt the user. |
-| `skill-families:<list>` | Comma-separated families to symlink (`security`, 
`pr-management`). Default on `adopt`: prompt. Default on `upgrade`: read the 
families list from `<committed-lock>` / `<local-lock>` and **ensure every 
framework skill in those families has a valid symlink** — create or repair 
missing / broken symlinks, not just add new ones. |
+| `skill-families:<list>` | Comma-separated **opt-in** families to symlink 
(`security`, `pr-management`). Default on `adopt`: prompt. Default on 
`upgrade`: read the families list from `<committed-lock>` / `<local-lock>` and 
**ensure every framework skill in those families has a valid symlink** — create 
or repair missing / broken symlinks, not just add new ones. The flag never 
accepts the always-on families (`setup-*` minus `setup-steward` itself, and 
`list-steward-*`); per [Golden rule 8 [...]
 | `--purge-overrides` | *(unadopt only)* Also `git rm -r` 
`.apache-steward-overrides/`. Default: preserve. |
 | `dry-run` | Show what the skill would do without writing anything. |
 
diff --git a/.claude/skills/setup-steward/adopt.md 
b/.claude/skills/setup-steward/adopt.md
index 01fcc94..f7a3183 100644
--- a/.claude/skills/setup-steward/adopt.md
+++ b/.claude/skills/setup-steward/adopt.md
@@ -35,8 +35,14 @@ between automatically:
   version (overrides the prompt).
 - `method:<git-branch | git-tag | svn-zip>` — explicit method
   (overrides the prompt).
-- `skill-families:<list>` — comma-separated families to
-  symlink (default: prompt).
+- `skill-families:<list>` — comma-separated **opt-in**
+  families to symlink (default: prompt). Valid values:
+  `security`, `pr-management`. The flag does **not** accept
+  the always-on families (`setup-*` minus `setup-steward`
+  itself, and `list-steward-*`); per
+  [`SKILL.md` Golden rule 8](SKILL.md#golden-rules) those
+  are wired up unconditionally on every adopt run and the
+  user is never asked about them.
 
 ## Step 0 — Pre-flight
 
@@ -131,8 +137,56 @@ fetch — the recipe ran first and left the snapshot in 
place.
 
 After the fetch (or skip), confirm
 `<snapshot-dir>/.claude/skills/` lists the framework skills
-(`pr-management-*`, `security-*`, `setup-*`). If not, the
-fetch produced an unexpected layout — surface and stop.
+(`pr-management-*`, `security-*`, `setup-*`,
+`list-steward-*`). If not, the fetch produced an unexpected
+layout — surface and stop.
+
+## Step 3b — Reconcile the committed `setup-steward` with the new snapshot + 
reload in-flight
+
+Per [`SKILL.md` Golden rule 9](SKILL.md#golden-rules), the
+adopter-side committed `setup-steward` skill must match the
+snapshot's version before the rest of this run executes —
+otherwise we finish adoption against the *old* bootstrap
+logic for a *new* framework version.
+
+1. Diff `<adopter-skills-dir>/setup-steward/` against
+   `.apache-steward/.claude/skills/setup-steward/`.
+2. If they match — skip the rest of this step.
+3. If they differ and the adopter has **no** local
+   modifications beyond what the snapshot ships — overwrite
+   the committed copy from the snapshot:
+
+   ```bash
+   # Flat layout:
+   rm -rf <adopter-skills-dir>/setup-steward
+   cp -r .apache-steward/.claude/skills/setup-steward \
+         <adopter-skills-dir>/setup-steward
+
+   # Double-symlinked layout: copy into .github/skills/ —
+   # the .claude/skills/setup-steward symlink already
+   # points at it.
+   ```
+
+4. If the adopter **does** have local modifications,
+   surface the diff and stop. The user either (a) confirms
+   the local mods can be discarded, (b) upstreams them as a
+   PR to `apache/airflow-steward` first, or (c) defers the
+   bootstrap-skill refresh — in (c) the rest of this run
+   continues against the in-flight (older) version with a
+   warning.
+5. **Reload in-flight.** Immediately after the copy lands,
+   re-read `<adopter-skills-dir>/setup-steward/SKILL.md`
+   and `<adopter-skills-dir>/setup-steward/adopt.md` (the
+   current sub-action file), plus any helper file already
+   open in this run (`conventions.md`, `overrides.md`),
+   before continuing to Step 4. The remaining steps run
+   against the just-loaded content.
+
+For a FRESH adoption where the bootstrap recipe placed the
+matching `setup-steward` content on disk before this skill
+was invoked, the diff in (1) is empty and this step is a
+no-op. For a SUBSEQUENT adoption against an old committed
+copy, the overwrite + reload is the common case.
 
 ## Step 4 — Write `<committed-lock>` (FRESH only)
 
@@ -153,19 +207,40 @@ ref:    <branch | tag | version>
 
 ## Step 5 — Pick the skill families
 
-(SUBSEQUENT adoption: re-use the families currently
-symlinked, if any. Or re-prompt if none.)
+The framework's family set splits into two tiers:
+
+**Always-on (no prompt; per
+[`SKILL.md` Golden rule 8](SKILL.md#golden-rules)):**
+
+- **`setup-*`** *(minus `setup-steward` itself)* — every
+  `setup-*` skill in the snapshot. Today:
+  `setup-isolated-setup-install`,
+  `setup-isolated-setup-update`,
+  `setup-isolated-setup-verify`, `setup-override-upstream`,
+  `setup-shared-config-sync`.
+- **`list-steward-*`** — every `list-steward-*` skill in
+  the snapshot. Today: `list-steward-skills`.
+
+These are wired up unconditionally; the user is **not**
+asked about them and they cannot be opted out via the
+`skill-families:` flag. The lock files do not record them
+because they are framework-mandated, not user-selected.
+
+**Opt-in (prompt, or read from
+`skill-families:` / the locks):**
+
+(SUBSEQUENT adoption: re-use the opt-in families currently
+recorded in `<committed-lock>` / `<local-lock>`, if any. Or
+re-prompt if none.)
 
-If `skill-families:` was passed, use those. Otherwise,
-prompt the user:
+If `skill-families:` was passed, use those values verbatim
+for the opt-in set. Otherwise prompt the user with:
 
 - **`security`** — eight skills for security-issue
   handling. Maintainer-only; not useful unless the project
   has a security tracker.
-- **`pr-management`** — three skills for maintainer-facing
+- **`pr-management`** — five skills for maintainer-facing
   PR queue work.
-- **`setup`** *(implicit)* — always installed because the
-  snapshot carries it.
 
 **Prefer structured Q&A.** When the agent harness offers a
 structured-question tool, use a *multi-select* prompt for
@@ -178,6 +253,10 @@ user named no family, default to selecting both for an
 adopter that is a maintainer-driven repo, or to no
 pre-selection otherwise. Free-form chat is the fallback.
 
+Do **not** offer `setup-*` or `list-steward-*` as
+selectable options in the prompt — they are wired up
+silently regardless of what the user picks here.
+
 ## Step 6 — Write `<local-lock>`
 
 Always written, both FRESH and SUBSEQUENT. Records what
@@ -204,24 +283,54 @@ idempotent — re-add them if they're missing.
 /.claude/skills/security-*
 /.claude/skills/pr-management-*
 /.claude/skills/setup-isolated-setup-*
+/.claude/skills/setup-override-upstream
 /.claude/skills/setup-shared-config-sync
+/.claude/skills/list-steward-*
 /.github/skills/security-*
 /.github/skills/pr-management-*
 /.github/skills/setup-isolated-setup-*
+/.github/skills/setup-override-upstream
 /.github/skills/setup-shared-config-sync
+/.github/skills/list-steward-*
 ```
 
+The `setup-override-upstream`, `setup-shared-config-sync`,
+`setup-isolated-setup-*`, and `list-steward-*` entries are
+the always-on families per
+[`SKILL.md` Golden rule 8](SKILL.md#golden-rules); they are
+gitignored on every adopter regardless of the opt-in
+family pick. `setup-steward` itself is **not** gitignored —
+it is the one committed framework skill.
+
 Mirror under `.github/skills/` only if the adopter uses the
 double-symlinked convention.
 
 ## Step 8 — Wire up the framework-skill symlinks
 
-For each skill family the user picked, walk
-`<snapshot-dir>/.claude/skills/` and create a gitignored
-symlink for every matching skill at
-`<adopter-skills-dir>/<skill>` → relative path into
+The skill walks `<snapshot-dir>/.claude/skills/` and creates
+a gitignored symlink for every framework skill the adopter
+should have callable, at `<adopter-skills-dir>/<skill>` →
+relative path into
 `<snapshot-dir>/.claude/skills/<skill>/`.
 
+The set of skills to link is the **union** of:
+
+1. **The opt-in families the user picked in Step 5**
+   (`security`, `pr-management`, or both). Each contributes
+   every framework skill in the snapshot whose name starts
+   with that family's prefix.
+2. **The always-on families** (no user input — per
+   [`SKILL.md` Golden rule 8](SKILL.md#golden-rules)):
+   every `setup-*` skill *except* `setup-steward` itself,
+   and every `list-steward-*` skill.
+
+The always-on set is added on every run, even when the user
+picked no opt-in families, even when `skill-families:` was
+passed with a narrow value, and even on the SUBSEQUENT-
+adoption path where the committed lock only records the
+opt-in pick. Compute the family glob fresh from the snapshot
+contents on disk — do not hard-code skill names.
+
 If the adopter uses the double-symlinked convention
 (see [`conventions.md`](conventions.md)), create both
 layers — the inner one in `.github/skills/` points at the
@@ -229,10 +338,17 @@ snapshot, the outer `.claude/skills/` points at the
 inner. Both gitignored.
 
 **Never overwrite an existing committed skill** of the same
-name. Surface conflicts and stop.
-
-Show the symlinks the skill is about to create, ask the
-user to confirm, then create them.
+name. Surface conflicts and stop. `setup-steward` itself is
+the one committed skill — the symlink wiring step skips it
+by name; the committed copy is reconciled in
+[Step 
3b](#step-3b--reconcile-the-committed-setup-steward-with-the-new-snapshot--reload-in-flight),
+not here.
+
+Show the symlinks the skill is about to create, grouped by
+*opt-in family* / *always-on family*, ask the user to
+confirm, then create them. Always-on entries are surfaced
+read-only — the prompt is "confirm this list" not "edit this
+list".
 
 ## Step 9 — Scaffold `.apache-steward-overrides/` (FRESH only)
 
@@ -541,10 +657,72 @@ Surface the rendered diff (`git diff README.md AGENTS.md`)
 to the user before writing. The user confirms once for the
 whole doc set; do not ask separately per file.
 
-## Step 12 — Sanity check
-
-Run [`verify.md`](verify.md)'s checklist as a final step.
-Every check should be ✓ before the skill reports success.
+## Step 12 — Post-install sync + worktree propagation + sanity check
+
+Three passes, in this order:
+
+1. **Sync hooks and config from the snapshot.** Walk every
+   hook or config file the framework ships that an adopter
+   is expected to carry locally — at minimum the
+   `post-checkout` hook installed in
+   [Step 10](#step-10--worktree-aware-post-checkout-hook-fresh-only),
+   plus any other adopter-side hook or config file the
+   framework adds in future. For each one, compare the
+   adopter's installed copy against the snapshot's expected
+   content; if drifted, re-install from the snapshot (after
+   surfacing the diff and asking for confirmation when the
+   local copy looks hand-edited). This is the "sync local
+   versions with the framework's latest" pass and runs
+   *every* time `/setup-steward` runs in either FRESH or
+   SUBSEQUENT adoption — it is the same pass `/setup-steward
+   upgrade` runs after a snapshot refresh.
+
+2. **Propagate to every worktree (run `worktree-init`
+   unconditionally).** The main is now adopted; any
+   pre-existing linked worktree of this repo still lacks
+   the snapshot symlink and the `<adopter-skills-dir>`
+   symlinks. `worktree-init` is **always run on every
+   worktree** at the end of adopt, even when none exist
+   yet, even when the worktree appears wired, because
+   `worktree-init` is idempotent and the cost of an
+   unnecessary run is trivially small. Conversely, *not*
+   running it leaves worktree state inconsistent with the
+   freshly-adopted main.
+
+   Procedure:
+
+   - Enumerate worktrees with
+     `git worktree list --porcelain`. Filter to linked
+     worktrees only — skip the main (already handled in
+     Steps 1–11 above) and skip any bare worktrees.
+   - If the list is empty, this pass is a no-op; record
+     "no linked worktrees" in the recap and continue.
+   - For each linked worktree, invoke
+     `/setup-steward worktree-init` with that worktree's
+     working directory as the `cwd`. The sub-action picks up
+     the family set from `<main>/.apache-steward.lock` plus
+     the always-on families per
+     [`SKILL.md` Golden rule 8](SKILL.md#golden-rules), and
+     reconciles both the snapshot symlink and the
+     `<adopter-skills-dir>` symlinks (see
+     [`worktree-init.md` Step 1 + Step 1b](worktree-init.md)).
+   - Collect each invocation's recap into a per-worktree
+     row in the adopt summary's `Worktrees:` section.
+
+   Do **not** abort adopt because one worktree failed — the
+   main is already adopted, and the failing worktree is
+   recorded in the summary for later resolution (typically:
+   the user `cd`s there and re-runs `/setup-steward
+   worktree-init` after merging the adoption commit
+   forward).
+
+3. **Run the verify checklist.** Invoke
+   [`verify.md`](verify.md)'s checks. Every check should be
+   ✓ before the skill reports success. The hook-content
+   drift check passes trivially because pass (1) just
+   refreshed the hook from the snapshot; the worktree
+   symlink checks pass trivially because pass (2) just
+   ran `worktree-init` everywhere.
 
 ## Output to the user
 
@@ -570,7 +748,10 @@ Committed (you'll see in `git status`):
 Gitignored (do NOT commit):
   .apache-steward/
   .apache-steward.local.lock
-  
.claude/skills/{security,pr-management,setup-isolated-setup,setup-shared-config-sync}-*
+  .claude/skills/{security,pr-management}-*            # opt-in families
+  .claude/skills/setup-isolated-setup-*                # always-on
+  .claude/skills/{setup-override-upstream,setup-shared-config-sync}  # 
always-on
+  .claude/skills/list-steward-*                        # always-on
   (and same patterns under .github/skills/ for double-symlinked layouts)
 ```
 
diff --git a/.claude/skills/setup-steward/upgrade.md 
b/.claude/skills/setup-steward/upgrade.md
index 680bf1a..db22206 100644
--- a/.claude/skills/setup-steward/upgrade.md
+++ b/.claude/skills/setup-steward/upgrade.md
@@ -91,9 +91,11 @@ For each kind of drift, present:
 - **`setup-steward` skill changed in the framework** —
   surface as a separate note. The adopter's *committed*
   copy of `setup-steward` will be auto-overwritten from the
-  new snapshot in [Step 
6b](#step-6b--overwrite-the-committed-setup-steward-skill-from-the-new-snapshot)
-  so the bootstrap stays in sync with the framework version
-  the project just pinned.
+  new snapshot in [Step 
4b](#step-4b--overwrite-the-committed-setup-steward-from-the-new-snapshot--reload-in-flight)
+  and then the skill **reloads in-flight** before the rest
+  of the upgrade runs, so the bootstrap stays in sync with
+  the framework version the project just pinned and the
+  remaining steps execute against the new content.
 
 Ask for explicit confirmation before deleting and re-
 installing.
@@ -134,6 +136,66 @@ new `<local-lock>`:
   HEAD` for git methods; the version for svn-zip.
 - `fetched_at` — current ISO-8601 timestamp.
 
+## Step 4b — Overwrite the committed `setup-steward` from the new snapshot + 
reload in-flight
+
+This step **must run before Steps 5+** so the remainder of
+this upgrade executes against the framework version the
+project just pinned to, not against the pre-upgrade
+bootstrap logic. It implements
+[`SKILL.md` Golden rule 9](SKILL.md#golden-rules).
+
+1. Compute the diff between the adopter-side
+   `<adopter-skills-dir>/setup-steward/` (committed copy)
+   and the snapshot's
+   `.apache-steward/.claude/skills/setup-steward/`.
+2. **If the adopter has local modifications** to their
+   committed copy beyond what the snapshot ships — surface
+   the diff and stop. Do **not** silently overwrite local
+   work. The user either (a) confirms the modifications can
+   be discarded, (b) decides to upstream them as a PR
+   against `apache/airflow-steward` first, or (c) defers
+   the bootstrap-skill update to a later upgrade run.
+3. **If there are no local modifications** (or the user
+   confirmed in 2), copy the snapshot's `setup-steward`
+   over the committed copy:
+
+   ```bash
+   # For the flat layout:
+   rm -rf .claude/skills/setup-steward
+   cp -r .apache-steward/.claude/skills/setup-steward \
+         .claude/skills/setup-steward
+
+   # For the double-symlinked layout (e.g. apache/airflow):
+   rm -rf .github/skills/setup-steward
+   cp -r .apache-steward/.claude/skills/setup-steward \
+         .github/skills/setup-steward
+   # The .claude/skills/setup-steward symlink does not need
+   # touching — it points at .github/skills/setup-steward
+   # which is now the new content.
+   ```
+
+4. **Reload in-flight.** Immediately after the copy lands —
+   before doing anything else in this run — re-read the
+   updated `<adopter-skills-dir>/setup-steward/SKILL.md`,
+   the just-overwritten `upgrade.md` (this file), and any
+   helper file you have already opened in this run
+   (`conventions.md`, `overrides.md`, `verify.md`). Resume
+   the upgrade from the step *after* this one, executing
+   the reloaded content — not the version of this file
+   that was in memory when the upgrade started.
+
+5. The new bootstrap-skill content lands as **modified files
+   in `git status`** at the adopter's committed-skill path.
+   The user reviews the diff and commits it as part of the
+   upgrade PR; on merge, every other contributor's next
+   `/setup-steward` run loads the matching version.
+
+The adopter shouldn't modify the bootstrap copy locally —
+the framework's hard rule is *"local mods go in
+`.apache-steward-overrides/`, framework changes go via PR
+to `apache/airflow-steward`"*. But if they did, step (2)
+catches it before the overwrite would erase their work.
+
 ## Step 5 — Reconcile overrides
 
 For each file in `<repo-root>/.apache-steward-overrides/`:
@@ -158,19 +220,37 @@ pattern-matching.
 
 ## Step 6 — Refresh framework-skill symlinks
 
-Read the chosen skill families from `<committed-lock>`
+Read the opt-in skill families from `<committed-lock>`
 (falling back to `<local-lock>` if the committed lock is
-silent on families). The post-upgrade state must be:
-*every framework skill in the new snapshot that belongs to
-a chosen family has a valid symlink in
-`<adopter-skills-dir>`*, and *no symlink points at a
-framework skill that no longer exists in the snapshot*.
+silent on families). Compose the **effective family set**
+for this upgrade as:
+
+- **Opt-in families** the project recorded (`security`,
+  `pr-management`, or both).
+- **Always-on families** (always added — never read from
+  the lock, never user-configurable, per
+  [`SKILL.md` Golden rule 8](SKILL.md#golden-rules)):
+  - every `setup-*` skill in the new snapshot *except*
+    `setup-steward` itself, and
+  - every `list-steward-*` skill in the new snapshot.
+
+Compute the always-on set fresh from the snapshot contents
+on disk — it expands automatically when the framework adds
+a new `setup-*` or `list-steward-*` skill in a release, and
+contracts on a rename / removal without code changes here.
+
+The post-upgrade state must be: *every framework skill in
+the new snapshot that belongs to the effective family set
+has a valid symlink in `<adopter-skills-dir>`*, and *no
+symlink points at a framework skill that no longer exists
+in the snapshot*.
 
 Run two passes:
 
 1. **Ensure every family-member skill is linked.** For each
-   framework skill in the new snapshot that belongs to a
-   chosen family, check `<adopter-skills-dir>/<skill>`:
+   framework skill in the new snapshot that belongs to the
+   effective family set, check
+   `<adopter-skills-dir>/<skill>`:
    - If the symlink exists and points at the matching
      snapshot path, leave it alone.
    - If it's missing, create it.
@@ -196,61 +276,103 @@ Run two passes:
 
 For the double-symlinked convention, refresh both layers.
 
-## Step 6b — Overwrite the committed `setup-steward` skill from the new 
snapshot
-
-The adopter-side committed `setup-steward` skill is the
-**only** framework skill that lives as a committed copy
-rather than a gitignored symlink (per
-[`SKILL.md` Golden rule 6](SKILL.md#golden-rules)). When the
-framework's `setup-steward` evolves — new sub-action, lock-
-format change, drift-detection refinement — the adopter's
-copy must follow, or the bootstrap on a fresh clone will run
-old logic against a new snapshot.
-
-This step keeps the two in sync **automatically on every
-upgrade**:
-
-1. Compute the diff between the adopter-side
-   `<adopter-skills-dir>/setup-steward/` (committed copy)
-   and the snapshot's
-   `.apache-steward/.claude/skills/setup-steward/`.
-2. **If the adopter has local modifications** to their
-   committed copy beyond what's in the snapshot — surface
-   the diff and stop. Do **not** silently overwrite local
-   work. The user either (a) confirms the modifications can
-   be discarded, (b) decides to upstream them as a PR
-   against `apache/airflow-steward` first, or (c) defers the
-   bootstrap-skill update to a later upgrade run.
-3. **If there are no local modifications** (or the user
-   confirmed in 2), copy the snapshot's `setup-steward`
-   over the committed copy:
-
-   ```bash
-   # For the flat layout:
-   rm -rf .claude/skills/setup-steward
-   cp -r .apache-steward/.claude/skills/setup-steward \
-         .claude/skills/setup-steward
-
-   # For the double-symlinked layout (e.g. apache/airflow):
-   rm -rf .github/skills/setup-steward
-   cp -r .apache-steward/.claude/skills/setup-steward \
-         .github/skills/setup-steward
-   # The .claude/skills/setup-steward symlink does not need
-   # touching — it points at .github/skills/setup-steward
-   # which is now the new content.
-   ```
-
-4. The new bootstrap-skill content lands as **modified files
-   in `git status`** at the adopter's committed-skill path.
-   The user reviews the diff and commits it as part of the
-   upgrade PR; on merge, every other contributor's next
-   `/setup-steward` run loads the matching version.
-
-The adopter shouldn't modify the bootstrap copy locally —
-the framework's hard rule is *"local mods go in
-`.apache-steward-overrides/`, framework changes go via PR
-to `apache/airflow-steward`"*. But if they did, this step
-catches it before the overwrite would erase their work.
+## Step 6b — Sync locally-installed hooks and configuration
+
+The framework ships hooks and config files an adopter
+*carries locally* (in the working tree or under `.git/`)
+rather than pulls in via symlink. Examples:
+
+- `<repo-root>/.git/hooks/post-checkout` (the worktree-aware
+  hook installed during adoption).
+- Any future hook or local config the framework adds.
+
+These can drift independently of the snapshot — an
+adopter who never re-runs `/setup-steward` after a
+framework upgrade keeps the old hook content even after the
+snapshot updates. This step closes that gap.
+
+For each hook / local config file the framework declares as
+"adopter-installed":
+
+1. Compute the snapshot's *expected* content for that file
+   (the framework ships the expected content under
+   `.apache-steward/.claude/skills/setup-steward/` or a
+   sibling location — locate the canonical source for each
+   file).
+2. Compare against the local copy.
+3. If unchanged — ✓, move on.
+4. If drifted and the diff is consistent with a stock
+   framework refresh (no operator hand-edits) — overwrite
+   silently.
+5. If the local copy looks hand-edited — surface the diff,
+   ask the user whether to overwrite, keep, or move-aside.
+
+Run this sync unconditionally on every upgrade and every
+adopt run, regardless of whether the snapshot changed. It
+catches the "local config drifted while the snapshot didn't"
+case (e.g. a contributor accidentally edited
+`.git/hooks/post-checkout`).
+
+## Step 6c — Propagate to every worktree (run `worktree-init` unconditionally)
+
+The main checkout drives the upgrade, but each worktree
+carries its own gitignored `<adopter-skills-dir>` symlinks.
+Those symlinks need refreshing too — otherwise a developer
+sitting in a worktree sees the new snapshot via the shared
+`<snapshot-dir>` symlink (per
+[`worktree-init.md`](worktree-init.md)) but their
+`<adopter-skills-dir>` may still point at *missing* skills
+(a family the upgrade added) or *renamed* ones (a framework
+rename).
+
+`worktree-init` is **always run on every worktree** at the
+end of an upgrade, even when the user did not ask for it,
+even when the worktree looks "already wired", because
+`worktree-init` is idempotent (a no-op when state is
+correct) and the cost of running it unnecessarily is
+trivially small. Conversely, *not* running it leaves worktree
+state inconsistent with the new framework version. The
+post-checkout hook covers the "next checkout" case, but
+upgrade re-aligns the existing worktrees **now**.
+
+Procedure:
+
+1. Enumerate worktrees with `git worktree list --porcelain`.
+   Filter to the linked worktrees only — skip the main
+   checkout (already handled above) and any bare worktrees.
+2. For each linked worktree, invoke
+   `/setup-steward worktree-init` with that worktree's
+   working directory as the `cwd`. The sub-action picks up
+   the family set from `<main>/.apache-steward.lock` (the
+   committed lock the worktree shares via git) plus the
+   always-on families per
+   [`SKILL.md` Golden rule 8](SKILL.md#golden-rules), and
+   reconciles both the snapshot symlink and the
+   `<adopter-skills-dir>` symlinks (see
+   [`worktree-init.md` Step 1 + Step 1b](worktree-init.md)).
+3. Collect each invocation's recap into a per-worktree row
+   for the upgrade summary's `Worktrees:` section
+   (Step 8 output block).
+
+**Failure handling per worktree:**
+
+- If a worktree is on a branch that does not carry the
+  adopter's committed `setup-steward` skill (e.g. a feature
+  branch from before adoption landed), the worktree-init
+  invocation refuses with "main checkout not adopted from
+  this branch's perspective". Surface as a ⚠ row in the
+  summary and continue with the next worktree — the user
+  resolves it later by merging the adoption commit forward.
+- If a worktree has a hand-maintained `<snapshot-dir>` that
+  is **not** a symlink to the main's, the move-aside flow
+  in [`worktree-init.md` Step 0 row 4](worktree-init.md)
+  asks for confirmation. Surface to the user; either
+  confirm and continue, or defer that worktree and move on.
+
+Do **not** abort the whole upgrade because one worktree
+failed — the main is already upgraded and the other
+worktrees still benefit from the propagation. The summary
+makes the skipped worktrees easy to come back to.
 
 ## Step 7 — Update `<local-lock>`
 
@@ -280,14 +402,30 @@ Drift remediated:
   Local:     <local.fetched-commit-or-version>  → <new>
   Snapshot:  refreshed at .apache-steward/
 
-Symlinks:
+setup-steward (bootstrap):
+  ✓ in sync   OR   ↻ overwritten from snapshot (reloaded in-flight)
+
+Symlinks (main checkout):
+  Opt-in families:     <security>, <pr-management>   (from lock)
+  Always-on families:  setup-*, list-steward-*       (per Golden rule 8)
   ✓ <list of unchanged symlinks>
-  + <list of newly-created symlinks (skill present in a chosen
-     family but missing from <adopter-skills-dir>)>
+  + <list of newly-created symlinks (skill present in the
+     effective family set but missing from <adopter-skills-dir>)>
   ↻ <list of repaired symlinks (existed but broken / pointing
      at the wrong path)>
   - <list of removed stale symlinks>
 
+Hooks + local config:
+  ✓ <list of files in sync>
+  ↻ <list of files re-synced from the snapshot>
+  ⚠ <list of files with hand-edits that need operator review>
+
+Worktrees (worktree-init was run on each, idempotently):
+  ✓ <worktree-path>   (snapshot symlink + family symlinks aligned)
+  ↻ <worktree-path>   (refreshed by worktree-init)
+  ⚠ <worktree-path>   (skipped — branch missing adopter's setup-steward)
+  - <none>            (when this repo has no linked worktrees)
+
 Overrides:
   ✓ <list of overrides whose target is unchanged>
   ⚠ <list of overrides flagged for re-anchoring> (open the
diff --git a/.claude/skills/setup-steward/verify.md 
b/.claude/skills/setup-steward/verify.md
index 32a59bb..6025dfc 100644
--- a/.claude/skills/setup-steward/verify.md
+++ b/.claude/skills/setup-steward/verify.md
@@ -129,9 +129,28 @@ into `.apache-steward/.claude/skills/<name>/`:
   or this same skill with `--auto-fix-symlinks`.
 
 For each framework skill in the snapshot **not** symlinked
-in the adopter — surface as ⚠ with the family
-classification. The user may have intentionally not picked
-that family; the warning prompts a decision.
+in the adopter, classify it:
+
+- **Always-on family** (every `setup-*` *except*
+  `setup-steward` itself, and every `list-steward-*` — per
+  [`SKILL.md` Golden rule 8](SKILL.md#golden-rules)) →
+  surface as ✗. These families are not opt-in; missing
+  symlinks here indicate a broken install or a skipped
+  upgrade pass. Remediation:
+  `/setup-steward verify --auto-fix-symlinks` (cheap), or
+  `/setup-steward upgrade` (covers the family-wide pass).
+- **Opt-in family the project picked** (per
+  `<committed-lock>` / `<local-lock>`) → surface as ✗. The
+  project declared the family but the install is missing a
+  skill from it. Remediation as above.
+- **Opt-in family the project did NOT pick** → surface as
+  ⚠. The user may have intentionally not picked that
+  family; the warning prompts a decision.
+
+The `--auto-fix-symlinks` path repairs the first two
+classes in place without prompting; the ⚠ class needs an
+explicit `/setup-steward adopt` re-run with the family
+added to the pick.
 
 ### 6. `.apache-steward-overrides/` exists + has the README
 
@@ -160,11 +179,14 @@ snapshot's 
`.apache-steward/.claude/skills/setup-steward/`.
     case after a framework upgrade where the adopter has
     not yet rerun `/setup-steward upgrade`). Run
     `/setup-steward upgrade` — its
-    [Step 
6b](upgrade.md#step-6b--overwrite-the-committed-setup-steward-skill-from-the-new-snapshot)
+    [Step 
4b](upgrade.md#step-4b--overwrite-the-committed-setup-steward-from-the-new-snapshot--reload-in-flight)
     auto-overwrites the committed copy with the snapshot's
-    version, surfaces local modifications first if any
-    exist, and lands the change in `git status` for the
-    user to commit.
+    version, **reloads the skill in-flight** so the rest of
+    the upgrade run executes against the new bootstrap
+    content (per
+    [`SKILL.md` Golden rule 9](SKILL.md#golden-rules)),
+    surfaces local modifications first if any exist, and
+    lands the change in `git status` for the user to commit.
   - **Committed copy is newer than the snapshot** (the
     adopter modified the bootstrap skill directly; an
     anti-pattern per the framework's hard rule). The
@@ -173,16 +195,34 @@ snapshot's 
`.apache-steward/.claude/skills/setup-steward/`.
     is to revert the modifications and use
     `.apache-steward-overrides/` instead.
 
-### 8. Post-checkout hook installed
-
-`<repo-root>/.git/hooks/post-checkout` exists, is
-executable, and contains the
-`/setup-steward verify --auto-fix-symlinks` recipe.
-
-- ⚠ if missing — strictly optional, but worktrees off this
-  repo will need a manual
-  `/setup-steward verify --auto-fix-symlinks` after
-  checkout. Print the install recipe.
+### 8. Post-checkout hook installed *and content matches the framework's 
expected*
+
+Two sub-checks on `<repo-root>/.git/hooks/post-checkout`:
+
+1. **Presence + executable.** File exists, is executable,
+   and contains the
+   `/setup-steward verify --auto-fix-symlinks` recipe.
+   - ⚠ if missing — strictly optional, but worktrees off
+     this repo will need a manual
+     `/setup-steward verify --auto-fix-symlinks` after
+     checkout. Print the install recipe.
+
+2. **Content drift vs the framework's expected.** Diff the
+   installed hook against the framework's expected hook
+   content (the canonical source is shipped under the
+   snapshot — locate it during the check). Same logic
+   applies for any other adopter-installed local hook or
+   config file the framework grows in future.
+   - ✓ if content matches.
+   - ⚠ if drifted and the diff looks like operator
+     hand-edits — surface the diff; remediation is to run
+     `/setup-steward` (adopt or upgrade), whose
+     hook+config-sync pass re-installs from the snapshot
+     after asking about hand-edits.
+   - ✗ if drifted and the installed content is clearly
+     stale (older framework version's recipe) — same
+     remediation, no operator prompt needed; the sync
+     pass overwrites silently.
 
 ### 9. Project documentation mentions the framework
 
diff --git a/.claude/skills/setup-steward/worktree-init.md 
b/.claude/skills/setup-steward/worktree-init.md
index a82c1f6..6d9a71f 100644
--- a/.claude/skills/setup-steward/worktree-init.md
+++ b/.claude/skills/setup-steward/worktree-init.md
@@ -57,7 +57,7 @@ has the right symlink is a no-op.
    | Symlink to **something else** | Step 1 with a move-aside warning. The 
skill backs the existing link up, names what it pointed at, and asks the user 
to confirm before replacing. |
    | Regular directory (per-worktree snapshot from before this convention) | 
Step 1 with a move-aside warning. Back up the directory to 
`.apache-steward.bak.<timestamp>` and create the symlink. **Do not** `rm -rf` 
without confirmation — the directory may hold uncommitted local edits the 
operator wants to preserve before the framework standardised on 
snapshot-from-main. |
 
-## Step 1 — Create the symlink
+## Step 1 — Create the snapshot symlink
 
 ```bash
 ln -s <main>/.apache-steward <worktree>/.apache-steward
@@ -69,19 +69,61 @@ Then verify the chain end-to-end:
   at `<main>/.apache-steward`.
 - `ls <worktree>/.apache-steward/.claude/skills/` lists the
   same skills as `ls <main>/.apache-steward/.claude/skills/`.
-- Pick any committed skill symlink (e.g.
-  `<worktree>/.claude/skills/security-issue-sync/SKILL.md`) and
-  confirm `readlink -f` resolves it into
-  `<main>/.apache-steward/...` rather than dangling.
+
+## Step 1b — Wire up the worktree's `<adopter-skills-dir>` symlinks
+
+The snapshot symlink in Step 1 only makes the framework's
+*source* available to this worktree. The `<adopter-skills-dir>`
+symlinks (the gitignored per-skill entries the agent harness
+actually resolves) are **per-worktree** — each working copy
+needs its own. A worktree branched from before adoption
+landed, or branched from a state where the symlinks were
+cleaned, has none on disk.
+
+Compose the **effective family set** for this worktree:
+
+- **Opt-in families** the project recorded — read from
+  `<main>/.apache-steward.lock` (the committed lock; the
+  worktree shares it via git).
+- **Always-on families** — every `setup-*` skill in the
+  snapshot *except* `setup-steward` itself, and every
+  `list-steward-*` skill, per
+  [`SKILL.md` Golden rule 8](SKILL.md#golden-rules). These
+  are added unconditionally, never read from the lock.
+
+For each framework skill in the effective family set:
+
+- If `<worktree>/<adopter-skills-dir>/<skill>` is missing —
+  create it (gitignored).
+- If it exists and points at the correct snapshot path —
+  leave it alone.
+- If it exists but is broken or points at the wrong path —
+  repair it.
+
+Reuse the convention detection from
+[`conventions.md`](conventions.md): flat vs double-symlinked
+layout drives where the inner / outer links land. Both
+layers gitignored.
+
+Pick any framework skill symlink that should now exist (e.g.
+`<worktree>/.claude/skills/security-issue-sync/SKILL.md`) and
+confirm `readlink -f` resolves it into
+`<main>/.apache-steward/...` rather than dangling — same
+sanity check as Step 1's bottom bullet, just now end-to-end
+from agent-harness path through the worktree's symlink
+through the snapshot symlink to the framework source.
 
 ## Step 2 — Recap
 
 Print a short summary:
 
-- The symlink that was just created.
+- The snapshot symlink that was just created or confirmed.
 - The main checkout's resolved path.
 - The framework version the main is pinned at (read from
   `<main>/.apache-steward.lock`).
+- The effective family set wired in Step 1b, split into
+  *opt-in* and *always-on*, with per-skill ✓ / + / ↻
+  counts.
 - A reminder: `upgrade` from the main, not from the worktree.
 
 ## Inputs


Reply via email to