You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2026/05/04 09:38:42 UTC
(airflow-steward) branch main updated: feat(setup-steward): multi-install + committed/local lock split + drift detection (#40)
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 d58fec6 feat(setup-steward): multi-install + committed/local lock split + drift detection (#40)
d58fec6 is described below
commit d58fec6e62b7fafb3222bbf2c689ef4889c6c805
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Mon May 4 11:38:35 2026 +0200
feat(setup-steward): multi-install + committed/local lock split + drift detection (#40)
Rewrite setup-steward to support three install methods, split
the lock file into committed (project pin) and local
(per-machine fetch), and drive every framework-skill invocation
through a drift check that compares the two locks.
Three install methods (copy-pasteable recipes in
docs/setup/install-recipes.md)
- svn-zip: signed zip from ASF dist
(`https://dist.apache.org/repos/dist/release/<project>/`),
the canonical ASF release distribution path. Verified via
SHA-512 + OpenPGP. Recommended for production once the
framework ships official releases.
- git-tag: pinned to a specific git tag — frozen by tag.
- git-branch: tracks a branch tip (default: main) — the WIP
path during pre-release.
Each recipe is a single shell block: adds the right gitignore
entries, downloads + verifies + extracts, copies the
setup-steward skill into the adopter's skills dir (matching the
flat or double-symlinked convention), and tells the user to
follow /setup-steward from there.
Lock-file split
- .apache-steward.lock (committed) — the project's pin: method,
url, ref, plus a verification anchor (commit SHA for tag,
sha512 for zip). Edited only by /setup-steward; bumping it is
a deliberate project-level commit.
- .apache-steward.local.lock (gitignored) — what THIS machine
actually fetched: source_method, source_url, source_ref,
fetched_commit, fetched_at. Per-developer, per-checkout.
Drift detection
Every framework skill (and /setup-steward verify) compares the
local lock to the committed lock at the top of its run. On
mismatch:
- Method or URL differ → ✗ full re-install
- Ref differs (project bumped tag, or git-branch local is
behind upstream) → ⚠ sync
- svn-zip SHA-512 differs from the committed anchor → ✗
security-flagged; investigate before upgrading
The proposal is always /setup-steward upgrade. On user OK,
upgrade:
1. Deletes the gitignored snapshot outright.
2. Re-installs per the committed lock.
3. Refreshes gitignored framework-skill symlinks (adds new
skills, removes renamed-away ones).
4. Reconciles agentic overrides against the new framework
structure (surfaces conflicts; never auto-rewrites).
5. Updates the local lock.
Changes
- Rewrite setup-steward/SKILL.md, adopt.md, upgrade.md,
verify.md to express the new model.
- New: docs/setup/install-recipes.md with the three
copy-pasteable recipes + explanatory framing.
- README's Adopting the framework section split into
Bootstrap (shell) + Skill takeover + Subsequent
contributors + Drift detection.
Out of scope (follow-up): wire the drift check into each
framework skill's pre-flight section (alongside the
override-consultation hook from #39). 15 skills to touch;
mechanical, deferred to keep this PR focused on the
mechanism.
Generated-by: Claude Code (Claude Opus 4.7)
---
.claude/skills/setup-steward/SKILL.md | 239 +++++++++++++-------
.claude/skills/setup-steward/adopt.md | 377 +++++++++++++++++++-------------
.claude/skills/setup-steward/upgrade.md | 254 +++++++++++++--------
.claude/skills/setup-steward/verify.md | 166 ++++++++------
README.md | 99 +++++++--
docs/setup/install-recipes.md | 247 +++++++++++++++++++++
6 files changed, 972 insertions(+), 410 deletions(-)
diff --git a/.claude/skills/setup-steward/SKILL.md b/.claude/skills/setup-steward/SKILL.md
index 12c5952..75dc65f 100644
--- a/.claude/skills/setup-steward/SKILL.md
+++ b/.claude/skills/setup-steward/SKILL.md
@@ -8,19 +8,22 @@ description: |
gitignored snapshot this skill manages. Sub-actions:
`/setup-steward` — first-time adoption (default)
`/setup-steward upgrade` — refresh the gitignored snapshot
- `/setup-steward verify` — health check the integration
+ per the committed lock
+ `/setup-steward verify` — health check + drift detection
`/setup-steward override <skill>` — open or scaffold an
agentic override for a framework
skill in `.apache-steward-overrides/`
when_to_use: |
Invoke when the user says "adopt apache-steward", "adopt
- apache/airflow-steward", "set up steward in this repo", or
- the agent equivalent triggered by following the framework's
- README adoption instructions. Also for periodic maintenance:
- "upgrade steward", "verify steward setup", "update the
- steward snapshot". This is the only framework skill that
- should be **copied** into an adopter's repo (every other
- framework skill is a symlink the adopt sub-action wires up).
+ apache/airflow-steward", "set up steward in this repo",
+ "follow .claude/skills/setup-steward", or the agent
+ equivalent triggered by following the framework's README
+ adoption instructions. Also for periodic maintenance:
+ "upgrade steward", "verify steward setup", "check steward
+ drift", "the snapshot is stale". This is the only framework
+ skill that should be **copied** into an adopter's repo
+ (every other framework skill is a symlink the adopt
+ sub-action wires up).
license: Apache-2.0
---
@@ -30,11 +33,12 @@ license: Apache-2.0
<!-- Placeholder convention (see ../../AGENTS.md#placeholder-convention-used-in-skill-files):
<project-config> → adopter's `.apache-steward-overrides/` directory
<snapshot-dir> → `.apache-steward/` (gitignored snapshot of the framework)
+ <committed-lock> → `.apache-steward.lock` (committed — project's pin)
+ <local-lock> → `.apache-steward.local.lock` (gitignored — per-machine record)
<upstream> → adopter's public source repo (the repo this skill is being run in)
<framework-source> → the apache-steward source we download a snapshot from
- (currently `https://github.com/apache/airflow-steward.git`,
- later `https://downloads.apache.org/airflow/...` (e.g.) once
- official ASF releases ship). -->
+ — one of: signed zip from ASF dist, git tag, git branch.
+ See [`docs/setup/install-recipes.md`](../../../docs/setup/install-recipes.md). -->
# setup-steward
@@ -43,13 +47,26 @@ project commits**. Every other apache-steward skill (security,
pr-management) is a gitignored symlink into the gitignored
snapshot at `<snapshot-dir>` that this skill manages.
-The adoption model is **snapshot + agentic overrides** (not
-submodule, not marketplace, not vendored copy):
+The adoption model is **snapshot + agentic overrides + drift-
+aware updates** (not submodule, not marketplace, not vendored
+copy):
-- The framework is downloaded as a `--depth=1` git checkout (or,
- once official ASF releases ship, a signed tarball) into
- `<snapshot-dir>` and **gitignored** in the adopter repo. The
- snapshot is a build artefact, not source.
+- The framework is downloaded into `<snapshot-dir>` and
+ **gitignored** in the adopter repo. The snapshot is a build
+ artefact, not source.
+- Three install methods are supported (see
+ [`docs/setup/install-recipes.md`](../../../docs/setup/install-recipes.md)
+ for verbatim copy-pasteable recipes):
+ - **svn-zip** — released, signed zip from ASF distribution
+ (recommended for production once releases ship).
+ - **git-tag** — pinned to a specific git tag.
+ - **git-branch** — tracks a branch tip (default: `main`,
+ the WIP path).
+- **Two lock files** record the framework version. The
+ committed one declares what the project pins to; the local
+ one records what each machine actually fetched. Drift
+ between them is surfaced and remediated by
+ `/setup-steward upgrade`.
- Symlinks from the adopter's skill directory into
`<snapshot-dir>/.claude/skills/<framework-skill>/` make the
framework's skills callable as if they lived in the adopter
@@ -65,14 +82,68 @@ submodule, not marketplace, not vendored copy):
[`docs/setup/agentic-overrides.md`](../../../docs/setup/agentic-overrides.md)
for the design rationale.
+## The two lock files
+
+The framework's lock-file model splits **what the project pins
+to** (committed) from **what this machine actually fetched**
+(local). This split is the foundation of drift detection and
+the multi-installer support.
+
+### `<committed-lock>` — `.apache-steward.lock`
+
+Committed at the adopter repo root. The **project's pin**.
+Edited only by `/setup-steward`; do not modify by hand.
+
+```text
+# .apache-steward.lock — committed; the project's pin.
+
+method: <git-branch | git-tag | svn-zip>
+url: <see per-method format below>
+
+# For method=git-branch:
+ref: main
+
+# For method=git-tag:
+ref: v1.0.0 # the tag name
+commit: <SHA> # the commit the tag pointed to when committed
+
+# For method=svn-zip:
+ref: 1.0.0 # the version number
+sha512: <hash> # the released zip's SHA-512 (for re-fetch verification)
+```
+
+The next adopter who runs `/setup-steward adopt` reads this
+file and re-installs to the **same version** the project
+declared. This is the core of the "adopt once, all subsequent
+users get the same thing" promise.
+
+### `<local-lock>` — `.apache-steward.local.lock`
+
+Gitignored at the adopter repo root. The **local snapshot's
+fingerprint**. Records what this machine fetched and when.
+
+```text
+# .apache-steward.local.lock — gitignored; per-machine.
+
+source_method: <git-branch | git-tag | svn-zip>
+source_url: <URL the snapshot was actually fetched from>
+source_ref: <branch / tag / version actually fetched>
+fetched_commit: <commit SHA on disk now>
+fetched_at: <ISO-8601 timestamp>
+```
+
+The drift check on every framework-skill invocation compares
+this against `<committed-lock>` and surfaces any mismatch as a
+proposed `/setup-steward upgrade`.
+
## Detail files in this directory
| File | Purpose |
|---|---|
-| [`adopt.md`](adopt.md) | First-time adoption walk-through — detect the adopter's skills-dir convention, download the snapshot, set up `.gitignore`, create the framework-skill symlinks, scaffold `.apache-steward-overrides/`, update the adopter's project docs. The default sub-action. |
-| [`upgrade.md`](upgrade.md) | Refresh the gitignored snapshot to a newer framework version, reconcile any agentic overrides against the new framework structure, surface conflicts. |
-| [`verify.md`](verify.md) | Read-only health check — snapshot present + intact, symlinks point at live targets, `.gitignore` correct, `.apache-steward-overrides/` exists, the `setup-steward` skill itself is current. |
-| [`conventions.md`](conventions.md) | Adopter skills-dir convention auto-detection — flat `.claude/skills/<name>/`, the `.claude/skills/<name>` → `.github/skills/<name>/` double-symlink pattern (e.g. apache/airflow), or neither yet. |
+| [`adopt.md`](adopt.md) | First-time adoption walk-through — recognise existing-snapshot vs needs-bootstrap, write the two lock files, ask the user which skill families to wire up, create the gitignored symlinks, scaffold `.apache-steward-overrides/`, install the post-checkout hook, update project docs. The default sub-action. |
+| [`upgrade.md`](upgrade.md) | Refresh the gitignored snapshot per the committed lock, reconcile any agentic overrides + symlinks against the new framework structure, surface conflicts. Drives the on-drift remediation flow. |
+| [`verify.md`](verify.md) | Read-only health check — snapshot present + intact, both lock files in sync, symlinks point at live targets, `.gitignore` correct, `.apache-steward-overrides/` exists, drift status (committed vs local), the `setup-steward` skill itself is current. |
+| [`conventions.md`](conventions.md) | Adopter skills-dir convention auto-detection — flat `.claude/skills/<n>/`, the `.claude/skills/<n>` → `.github/skills/<n>/` double-symlink pattern (e.g. apache/airflow), or neither yet. |
| [`overrides.md`](overrides.md) | Agentic-override file management — open / scaffold an override for a framework skill, list existing overrides, help reconcile when the framework changes the underlying skill's structure on upgrade. |
## Golden rules
@@ -83,54 +154,72 @@ only** from an adopter's perspective. Every modification an
adopter wants must go into `.apache-steward-overrides/` (where
it is *committed* and survives the next `upgrade`). The skill,
and any other framework skill consulting overrides at run-time,
-**never** writes to `<snapshot-dir>`. If the user wants to
-upstream a framework change, the agent reads the latest
-`apache/airflow-steward` `main`, implements the change there,
-and opens a PR against the framework repo.
+**never** writes to `<snapshot-dir>`.
+
+**Golden rule 2 — `<committed-lock>` is the project's pin;
+`<local-lock>` is per-machine truth.** They serve different
+purposes and live in different places:
+
+- `<committed-lock>` declares what version the *project* uses.
+ Edited by the adopter who runs `/setup-steward adopt` first
+ (or who later runs `/setup-steward upgrade` and accepts the
+ new pin). Bumping it is a deliberate project-level action;
+ the bump shows up in the `git diff` of the PR that proposed
+ it.
+- `<local-lock>` records what *this machine* installed. Updated
+ silently by `/setup-steward adopt` and `/setup-steward
+ upgrade`. Per-developer, per-checkout, per-worktree.
+
+**Golden rule 3 — drift surfaces, drift gets remediated.**
+Every framework skill (and `/setup-steward verify`) checks
+`<committed-lock>` vs `<local-lock>` at the top of its run.
+On mismatch the skill surfaces the gap and proposes
+`/setup-steward upgrade`. The user accepts or defers; if they
+accept, `upgrade`:
-**Golden rule 2 — `.gitignore` keeps the adopter repo clean.**
+1. Deletes `<snapshot-dir>` outright.
+2. Re-installs per the *committed* lock (the new version the
+ project chose).
+3. Refreshes the gitignored framework-skill symlinks — adds
+ any new framework skills the user's family pick covers,
+ removes any framework skills that were renamed away or
+ removed.
+4. Reconciles agentic overrides against the new framework
+ structure (surfaces conflicts; never auto-rewrites).
+5. Updates `<local-lock>` to the new fetch.
+
+**Golden rule 4 — `.gitignore` keeps the adopter repo clean.**
Three things gitignored in the adopter repo:
-- `<snapshot-dir>` (the entire framework snapshot)
-- the symlinks `setup-steward adopt` creates in the adopter's
+- `<snapshot-dir>` (the entire framework snapshot — gigabytes
+ potentially).
+- `<local-lock>` (per-machine state).
+- The symlinks `setup-steward adopt` creates in the adopter's
skills directory (they target the gitignored snapshot, so
- they would dangle in a fresh clone)
-- the adopter's own scratch artefacts that other framework
- skills might create (`/tmp/...` style state caches)
+ they would dangle in a fresh clone).
**Committed**: this skill (`setup-steward`), the
-`.apache-steward-overrides/` directory, the `.gitignore`
-entries themselves, any project-doc updates the `adopt`
-sub-action makes.
+`<committed-lock>`, the `.apache-steward-overrides/`
+directory, the `.gitignore` entries themselves, any
+project-doc updates the `adopt` sub-action makes.
-**Golden rule 3 — follow the adopter's existing skills-dir
+**Golden rule 5 — follow the adopter's existing skills-dir
convention.** Different ASF projects already organise their
`.claude/skills/` differently (see
-[`conventions.md`](conventions.md)):
-
-- **flat**: `.claude/skills/<name>/SKILL.md` — directly in the
- Claude Code-discovered location.
-- **double-symlinked** (e.g. apache/airflow today): the actual skill
- content lives under `.github/skills/<name>/` and
- `.claude/skills/<name>` is a symlink into it. Claude Code
- discovers via `.claude/skills/`; the user maintains under
- `.github/skills/`.
-
-The `adopt` sub-action detects which pattern is in place and
-matches it. **The framework's symlinks land at the same depth
-as the adopter's existing skills**, not one level off.
+[`conventions.md`](conventions.md)). The `adopt` sub-action
+detects which pattern is in place and matches it.
-**Golden rule 4 — copy this skill, symlink the rest.** This
+**Golden rule 6 — copy this skill, symlink the rest.** This
skill (`setup-steward`) is the **only** framework skill that
gets copied into an adopter repo. All other framework skills
-(`security-issue-import`, `pr-management-triage`, etc.) are
-symlinked into the gitignored snapshot. Mixing the two — for
-example, copying a security skill — creates a maintenance
-hazard: copies drift from the framework's source-of-truth, and
-agentic overrides (which assume the framework version is the
-one in the snapshot) silently mis-apply.
-
-**Golden rule 5 — agentic overrides are read at run-time.**
+are symlinked into the gitignored snapshot. Mixing the two —
+copying a security skill, for instance — creates a
+maintenance hazard: copies drift from the framework's source-
+of-truth, and the drift-detection mechanism (which assumes
+the framework version is the one in `<snapshot-dir>`)
+silently mis-applies.
+
+**Golden rule 7 — agentic overrides are read at run-time.**
Every framework skill that supports overrides starts its run
by checking `.apache-steward-overrides/<this-skill>.md` for
adopter-specific instructions and applying them before
@@ -146,26 +235,24 @@ The skill dispatches by the first positional argument:
| Invocation | Loads | Purpose |
|---|---|---|
-| `/setup-steward` (no args) | [`adopt.md`](adopt.md) | First-time adoption (default). |
+| `/setup-steward` (no args) | [`adopt.md`](adopt.md) | First-time adoption (default). Idempotent — re-running on an already-adopted repo behaves like `verify`. |
| `/setup-steward adopt` | [`adopt.md`](adopt.md) | Same as no-arg — explicit form. |
-| `/setup-steward upgrade` | [`upgrade.md`](upgrade.md) | Refresh snapshot + reconcile overrides. |
-| `/setup-steward verify` | [`verify.md`](verify.md) | Read-only health check. |
+| `/setup-steward upgrade` | [`upgrade.md`](upgrade.md) | Refresh snapshot per `<committed-lock>` + reconcile overrides + refresh symlinks. |
+| `/setup-steward verify` | [`verify.md`](verify.md) | Read-only health check + drift status report. |
| `/setup-steward override <skill>` | [`overrides.md`](overrides.md) | Open / scaffold an override file. |
-If the snapshot is missing (no `<snapshot-dir>/`), the skill
-treats that as `adopt` regardless of which sub-action was
-named — the user has invoked on a repo that has not yet been
-adopted, and the right next step is to walk through adoption.
+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
+first, then continue.
## Inputs
-The skill is mostly driven by detection (it reads the adopter
-repo's state) but accepts these optional flags:
-
| Flag | Effect |
|---|---|
-| `from:<git-ref>` | Adopt / upgrade from a specific framework `<git-ref>` (branch, tag, or commit SHA) instead of `main`. Useful for testing a framework PR locally before it merges. |
-| `skill-families:<list>` | Comma-separated list of skill families to symlink (`security`, `pr-management`). Default on adopt: prompt the user. Default on upgrade: re-symlink the families currently linked. |
+| `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`: re-symlink the families currently linked. |
| `dry-run` | Show what the skill would do without writing anything. |
## What this skill is NOT for
@@ -173,12 +260,8 @@ repo's state) but accepts these optional flags:
- Not for installing the secure agent setup (sandbox, hooks,
pinned tools). That is
[`setup-isolated-setup-install`](../setup-isolated-setup-install/SKILL.md).
- The two are independent: an adopter can have steward set up
- but no isolated-setup wired (run setup-isolated-setup-install
- to fix), or have isolated-setup wired against a stale
- snapshot (run `setup-steward upgrade`).
- Not for upgrading framework tools installed on the host
- (`bubblewrap`, `socat`, `claude-code` itself). Those go via
+ (`bubblewrap`, `socat`, `claude-code` itself). That is
[`setup-isolated-setup-update`](../setup-isolated-setup-update/SKILL.md).
- Not for syncing the user's `~/.claude-config` across
machines. That is
@@ -191,7 +274,7 @@ repo's state) but accepts these optional flags:
| Symptom | Likely cause | Remediation |
|---|---|---|
-| `/setup-steward verify` reports the snapshot present but the symlinks dangle | adopter ran a `git clone` but not `/setup-steward` after — symlinks are gitignored but persist in their target's absence | run `/setup-steward adopt` (it idempotently re-creates symlinks) |
-| `/setup-steward upgrade` surfaces conflicts in `.apache-steward-overrides/<skill>.md` | the framework restructured the skill in a way that invalidates an existing override | open the override file, follow the conflict markers, or invoke `/setup-steward override <skill>` to re-scaffold |
-| Worktree off the adopter repo can't find framework skills | worktrees off the adopter don't auto-inherit the gitignored snapshot | the `adopt` sub-action installs a `post-checkout` git hook that re-runs the snapshot install on worktree creation; verify the hook is present (`/setup-steward verify`) |
-| `git clone` of an upstream PR sees no framework skills | expected — the snapshot is gitignored, so a fresh clone has no `<snapshot-dir>`. The clone needs `/setup-steward` once before any framework skill is invocable | run `/setup-steward` from the cloned repo |
+| `/setup-steward verify` reports drift between committed and local locks | Project lead bumped `<committed-lock>` since this machine last fetched, or local snapshot is stale on a `main`-tracking adopter | `/setup-steward upgrade` |
+| Snapshot present but symlinks dangle | Adopter ran `git clone` but not `/setup-steward` after — symlinks are gitignored but persist in their target's absence on disk | `/setup-steward verify --auto-fix-symlinks` (or `/setup-steward adopt`, idempotent) |
+| Worktree off the adopter repo can't find framework skills | Worktrees off the adopter don't auto-inherit the gitignored snapshot | The `adopt` sub-action installs a `post-checkout` git hook that re-runs the snapshot install on worktree creation; verify the hook is present (`/setup-steward verify`) |
+| `git clone` of an upstream PR sees no framework skills | Expected — the snapshot is gitignored, so a fresh clone has no `<snapshot-dir>`. The clone needs `/setup-steward` once before any framework skill is invocable | `/setup-steward` |
diff --git a/.claude/skills/setup-steward/adopt.md b/.claude/skills/setup-steward/adopt.md
index ddb5c24..b1680e5 100644
--- a/.claude/skills/setup-steward/adopt.md
+++ b/.claude/skills/setup-steward/adopt.md
@@ -4,240 +4,303 @@
# adopt — first-time install of apache-steward into an adopter repo
The default sub-action when the user says "adopt apache-steward".
-Walks through detection, snapshot install, and the small set of
-adopter-side artefacts that need to land on disk.
+
+There are two adoption shapes the skill recognises and routes
+between automatically:
+
+- **Fresh adoption (no committed lock yet).** The first
+ adopter on a project. Runs the full bootstrap: pick the
+ install method, fetch the snapshot, write *both* lock
+ files, wire up symlinks, scaffold overrides, install
+ hooks, update docs.
+- **Subsequent adoption (committed lock exists).** A new
+ developer joining a project that already adopted. Reads
+ `<committed-lock>` to know what to install, fetches per
+ that pin, writes only the `<local-lock>`, refreshes
+ symlinks. Skips the doc-update + interactive-prompt flow.
+
+> **Note on the bootstrap recipe.** `setup-steward` is **the
+> only framework artefact an adopter commits**. Getting it
+> *into* a fresh adopter repo is the chicken-and-egg the
+> [install-recipes](../../../docs/setup/install-recipes.md)
+> doc resolves: copy-pasteable shell recipes per install
+> method that fetch the snapshot + place the `setup-steward`
+> skill content + add `.gitignore` entries. Once that
+> recipe runs and `setup-steward` is on disk, the agent
+> follows this file to finish adoption.
## Inputs
-- `from:<git-ref>` — adopt from a specific framework `<git-ref>`
- (default: `main` of `apache/airflow-steward`).
-- `skill-families:<list>` — comma-separated families to symlink
- in (`security`, `pr-management`). Default: prompt the user.
+- `from:<git-ref>` / `from:<version>` — explicit ref or
+ 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).
## Step 0 — Pre-flight
-1. Confirm we are in a git repo (`git rev-parse --show-toplevel`).
- If not, surface and stop — the user opened the agent in the
- wrong directory.
+1. Confirm we are in a git repo (`git rev-parse
+ --show-toplevel`).
2. Confirm we are **not** in `apache/airflow-steward` itself
- (read `git remote get-url origin` and refuse if it resolves
- to the framework). Adopting the framework into itself is a
- no-op the user did not intend.
+ (read `git remote get-url origin` and refuse if it
+ resolves to the framework).
3. Detect the adopter's existing skills-dir convention by
- following [`conventions.md`](conventions.md). The result
- pins which directory the framework symlinks land in
- (`<adopter-skills-dir>` from here on).
-
-## Step 1 — Pick the skill families
-
-If `skill-families:` was passed on the invocation, use those
-verbatim. Otherwise, present the families to the user and let
-them choose:
-
-- **`security`** — eight skills for security-issue handling
- (`security-issue-import`, `security-issue-sync`,
- `security-cve-allocate`, `security-issue-fix`, etc.).
- Maintainer-only; not useful unless the project has a
- security tracker.
-- **`pr-management`** — three skills for maintainer-facing PR
- queue work (`pr-management-triage`,
- `pr-management-stats`, `pr-management-code-review`).
-- **`setup`** *(implicit)* — the `setup-isolated-setup-*`,
- `setup-steward-*`, `setup-shared-config-sync` skills. The
- `setup` family is always installed because the snapshot
- carries it; the symlinks are wired up regardless of the
- user's other family picks.
-
-Show the user a short description of each family and ask which
-to install. Default to whichever family the user named in
-their initial "adopt" request (e.g. *"adopt apache-steward for
-PR triage"* → `pr-management`).
-
-## Step 2 — Download the snapshot
-
-Place the snapshot at `<repo-root>/.apache-steward/`. Use the
-WIP path for now (a `--depth=1` git checkout of the framework's
-`main` branch). The signed-tarball path
-(e.g. `https://downloads.apache.org/airflow/...` once ASF official
-releases ship per
-[release-policy](https://www.apache.org/legal/release-policy.html))
-is a future upgrade; both paths produce the same on-disk
-shape.
-
-```bash
-# WIP path — works today
-git clone --depth=1 \
- --branch <git-ref-or-main> \
- https://github.com/apache/airflow-steward.git \
- .apache-steward
+ following [`conventions.md`](conventions.md). Pin the
+ result as `<adopter-skills-dir>` for the rest of this
+ flow.
+
+## Step 1 — Detect adoption shape
+
+```text
+if .apache-steward.lock exists:
+ → SUBSEQUENT adoption
+elif .apache-steward/ exists (snapshot only):
+ → manual recipe was run; finish bootstrap (write committed
+ lock from the recipe's choices, then continue as FRESH
+ from Step 5)
+else:
+ → FRESH adoption
```
-If `<repo-root>/.apache-steward/` already exists with content,
-the user is in upgrade territory — refuse and suggest
-`/setup-steward upgrade` instead. (Idempotent re-run after a
-*partial* adopt is fine — see Step 6.)
+## Step 2 — Pick install method (FRESH only)
+
+If the user passed `method:` and `from:` flags, use those
+verbatim. Otherwise, prompt:
+
+| Method | When | Reproducibility |
+|---|---|---|
+| `svn-zip` | Production once ASF releases ship to dist | Frozen by version |
+| `git-tag` | Pin a specific tag | Frozen by tag |
+| `git-branch` | Track a branch tip (default: `main`) | Tracks tip — best during pre-release |
+
+The verbatim shell that fetches per each method is in
+[`docs/setup/install-recipes.md`](../../../docs/setup/install-recipes.md).
+The skill at this point can either:
+
+- Tell the user "your manual recipe already ran — please
+ confirm the method you used, I will record it in the
+ committed lock", or
+- Run the per-method fetch itself if `<snapshot-dir>` does
+ not yet exist.
-Pin the snapshot version into a small `.apache-steward.lock`
-file at the repo root (committed) — record the source URL,
-the resolved commit SHA, and the date. The `verify` and
-`upgrade` sub-actions read this file.
+For a SUBSEQUENT adoption (committed lock present), skip the
+prompt entirely — re-use the method/url/ref from the
+committed lock.
+
+## Step 3 — Fetch the snapshot (if not already on disk)
+
+Per the chosen method (FRESH) or per the committed lock
+(SUBSEQUENT):
+
+- **`git-branch`**: `git clone --depth=1 --branch <ref> <url>
+ .apache-steward`
+- **`git-tag`**: `git clone --depth=1 --branch <tag> <url>
+ .apache-steward`. After clone, capture the resolved commit
+ SHA for `<committed-lock>` (FRESH only).
+- **`svn-zip`**: `curl` the zip + `.sha512` + `.asc`,
+ verify, `unzip` to `.apache-steward/`. Re-fetch
+ verification details into `<committed-lock>` (FRESH only).
+
+If `<snapshot-dir>/` already exists with content, skip the
+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.
+
+## Step 4 — Write `<committed-lock>` (FRESH only)
+
+Create `<repo-root>/.apache-steward.lock`:
```text
-# .apache-steward.lock (committed)
-source: https://github.com/apache/airflow-steward.git
-ref: main
-commit: <SHA>
-fetched: <ISO-8601 date>
+# .apache-steward.lock — committed; the project's pin.
+# Edited only by /setup-steward; do not modify by hand.
+
+method: <method>
+url: <url>
+
+# Per-method fields:
+ref: <branch | tag | version>
+# git-tag: also `commit: <SHA>`
+# svn-zip: also `sha512: <hash>`
```
-## Step 3 — `.gitignore` entries
+## Step 5 — Pick the skill families
+
+(SUBSEQUENT adoption: re-use the families currently
+symlinked, if any. Or re-prompt if none.)
+
+If `skill-families:` was passed, use those. Otherwise,
+prompt the user:
+
+- **`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 queue work.
+- **`setup`** *(implicit)* — always installed because the
+ snapshot carries it.
-Add (if not already present) to `<repo-root>/.gitignore`:
+Default to whichever family the user named in their
+initial "adopt" request (e.g. *"adopt apache-steward for PR
+triage"* → `pr-management`).
+
+## Step 6 — Write `<local-lock>`
+
+Always written, both FRESH and SUBSEQUENT. Records what
+this machine fetched.
```text
-# apache-steward — gitignored snapshot of the framework, refreshed
-# by the setup-steward skill. The snapshot is a build artefact, not
-# source. To re-create: /setup-steward (in your agent of choice).
-/.apache-steward/
+# .apache-steward.local.lock — gitignored; per-machine.
-# Symlinks the setup-steward skill creates into the snapshot. They
-# would dangle on a fresh clone before /setup-steward is run.
+source_method: <method>
+source_url: <url>
+source_ref: <ref>
+fetched_commit: <commit SHA — for git-branch and git-tag>
+fetched_at: <ISO-8601 timestamp>
+```
+
+## Step 7 — `.gitignore` entries (FRESH only)
+
+The bootstrap recipe wrote these already; this step is
+idempotent — re-add them if they're missing.
+
+```text
+/.apache-steward/
+/.apache-steward.local.lock
/.claude/skills/security-*
/.claude/skills/pr-management-*
/.claude/skills/setup-isolated-setup-*
-/.claude/skills/setup-steward-*
/.claude/skills/setup-shared-config-sync
-# ...mirror the same patterns under .github/skills/ if the adopter
-# uses the double-symlinked convention (see conventions.md).
+/.github/skills/security-*
+/.github/skills/pr-management-*
+/.github/skills/setup-isolated-setup-*
+/.github/skills/setup-shared-config-sync
```
-Show the diff to the user before writing. The `setup-steward`
-skill itself (`*/setup-steward/`) is **not** gitignored — it
-is committed.
+Mirror under `.github/skills/` only if the adopter uses the
+double-symlinked convention.
-## Step 4 — Wire up the framework-skill symlinks
+## Step 8 — Wire up the framework-skill symlinks
-For each skill family the user picked plus the `setup` family,
-walk the snapshot's `.apache-steward/.claude/skills/` and create
-a gitignored symlink for every matching skill at
+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
-`.apache-steward/.claude/skills/<skill>/`.
+`<snapshot-dir>/.claude/skills/<skill>/`.
If the adopter uses the double-symlinked convention
-(`.claude/skills/<n>` → `.github/skills/<n>/` per
-[`conventions.md`](conventions.md)), create both layers — the
-inner one in `.github/skills/` points at the snapshot, the
-outer `.claude/skills/` points at the inner.
+(see [`conventions.md`](conventions.md)), create both
+layers — the inner one in `.github/skills/` points at the
+snapshot, the outer `.claude/skills/` points at the
+inner. Both gitignored.
-**Never overwrite an existing committed skill** of the same name.
-If the adopter repo already has e.g. `.github/skills/pr-triage`
-(an old-name in-repo copy), surface the conflict and stop. The
-user resolves manually — likely by deleting the stale copy and
-re-running.
+**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.
+Show the symlinks the skill is about to create, ask the
+user to confirm, then create them.
-## Step 5 — Scaffold `.apache-steward-overrides/`
+## Step 9 — Scaffold `.apache-steward-overrides/` (FRESH only)
-Create `<repo-root>/.apache-steward-overrides/` (directory) if
-it doesn't exist, with a small `README.md` inside that explains
-the contract:
+Create `<repo-root>/.apache-steward-overrides/` (directory)
+with a small `README.md` inside:
```markdown
# apache-steward overrides
-Agent-readable instructions that **override** specific steps or
-behaviours of the apache-steward framework's skills, scoped to
+Agent-readable instructions that override specific steps or
+behaviours of apache-steward framework skills, scoped to
this adopter repo. Each override file is named after the
framework skill it modifies (e.g. `pr-management-triage.md`
overrides the `pr-management-triage` skill).
-The framework skills consult this directory at run-time before
-executing default behaviour. See
+The framework skills consult this directory at run-time
+before executing default behaviour. See
[`docs/setup/agentic-overrides.md`](https://github.com/apache/airflow-steward/blob/main/docs/setup/agentic-overrides.md)
in the framework for the full contract.
**Hard rule**: never modify the snapshot under
-`<repo-root>/.apache-steward/`. Local mods go here. Framework
-changes go via PR to `apache/airflow-steward`.
+`<repo-root>/.apache-steward/`. Local mods go here.
+Framework changes go via PR to `apache/airflow-steward`.
```
-This directory is **committed** (the whole point is for
-overrides to ship with the adopter repo).
+This directory is **committed** (overrides ship with the
+adopter repo).
-## Step 6 — Worktree-aware post-checkout hook
+## Step 10 — Worktree-aware post-checkout hook (FRESH only)
-Install a `post-checkout` git hook at
+Install
`<repo-root>/.git/hooks/post-checkout` that re-creates the
-gitignored symlinks if a fresh worktree is checked out off
-this repo. (The snapshot itself is gitignored and won't follow
-the worktree, but the hook keeps the symlink shape consistent.)
-
-The hook is a one-liner that re-invokes
-`/setup-steward verify --auto-fix-symlinks` against the new
-worktree path.
-
-Surface the hook content to the user before writing.
+gitignored symlinks if a fresh worktree is checked out. The
+hook is a one-liner that re-invokes
+`/setup-steward verify --auto-fix-symlinks`. Surface the
+hook content to the user before writing.
-## Step 7 — Project doc updates
+## Step 11 — Project doc updates (FRESH only)
-Add (or extend) a brief paragraph in the adopter's `README.md`
-or `CONTRIBUTING.md` (whichever already mentions agents /
-skills) noting that this repo adopts apache-steward via the
-snapshot mechanism, and pointing at:
+Add (or extend) a brief paragraph in the adopter's
+`README.md` or `CONTRIBUTING.md` (whichever already mentions
+agents / skills) noting:
-- [`apache-steward`'s top-level README](https://github.com/apache/airflow-steward) for the framework's overview;
-- the local `.apache-steward-overrides/` for adopter-specific
- modifications.
+- the project adopts apache-steward via the snapshot
+ mechanism;
+- a fresh clone needs `/setup-steward` to populate the
+ framework before any framework skill is invocable;
+- adopter-specific modifications live in
+ `.apache-steward-overrides/`.
Surface the doc diff to the user before writing.
-## Step 8 — Sanity check
+## Step 12 — Sanity check
-Run [`verify.md`](verify.md)'s checklist as a final step. Every
-check should be ✓ before the skill reports success.
+Run [`verify.md`](verify.md)'s checklist as a final step.
+Every check should be ✓ before the skill reports success.
## Output to the user
A summary of what was written:
```text
-✓ Snapshot installed at .apache-steward/ (commit <SHA>)
-✓ .gitignore updated (.apache-steward/, .claude/skills/security-*, ...)
-✓ Symlinks created:
- .claude/skills/security-issue-import → .apache-steward/.claude/skills/security-issue-import/
- .claude/skills/security-issue-sync → ...
- ...
-✓ .apache-steward-overrides/ scaffold created (committed)
+✓ Method: <method>
+✓ Source: <url>@<ref>
+✓ Snapshot: .apache-steward/ (commit <SHA>)
+✓ Locks: .apache-steward.lock (committed) + .apache-steward.local.lock (gitignored)
+✓ Symlinks: <list of created symlinks>
+✓ Overrides scaffold: .apache-steward-overrides/ (committed)
✓ post-checkout hook installed
-✓ README.md updated with adoption note
+✓ <repo>/README.md updated with adoption note
Committed (you'll see in `git status`):
.gitignore
.apache-steward.lock
.apache-steward-overrides/README.md
- .claude/skills/setup-steward/ (this skill itself)
+ <adopter-skills-dir>/setup-steward/ (this skill itself)
README.md (or CONTRIBUTING.md)
Gitignored (do NOT commit):
.apache-steward/
- .claude/skills/security-*
- .claude/skills/pr-management-* (depending on family pick)
- ...
+ .apache-steward.local.lock
+ .claude/skills/{security,pr-management,setup-isolated-setup,setup-shared-config-sync}-*
+ (and same patterns under .github/skills/ for double-symlinked layouts)
```
-Then suggest the user `git add` the committed files and open a
-PR.
+Then suggest the user `git add` the committed files and open
+a PR.
## Failure modes
-- **Existing `<repo-root>/.apache-steward/`** → suggest
+- **Existing `<repo-root>/.apache-steward/` and
+ `<committed-lock>` are out of sync** → drift; suggest
`/setup-steward upgrade`.
-- **Existing committed skill conflicts with a framework skill
- symlink** → stop, name the conflict, let the user resolve.
-- **Network failure on the snapshot download** → stop, surface
- the curl/git error. The user retries.
-- **`.gitignore` already mentions `.apache-steward/` but no
- snapshot is present** → either a partial adopt or a manual
- cleanup. Re-run is safe; the skill detects this and proceeds.
+- **Existing committed skill conflicts with a framework
+ skill symlink** → stop, name the conflict, let the user
+ resolve.
+- **Network failure on the snapshot download** → stop,
+ surface the curl/git error.
+- **`<committed-lock>` references a method/URL the runtime
+ cannot reach** (e.g. svn-zip URL 404) → surface, ask the
+ user whether the project has retired that release; the
+ user updates `<committed-lock>` deliberately and re-runs.
diff --git a/.claude/skills/setup-steward/upgrade.md b/.claude/skills/setup-steward/upgrade.md
index 6434ed9..35d64d2 100644
--- a/.claude/skills/setup-steward/upgrade.md
+++ b/.claude/skills/setup-steward/upgrade.md
@@ -1,148 +1,220 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/legal/release-policy.html -->
-# upgrade — refresh the gitignored snapshot + reconcile overrides
+# upgrade — refresh the gitignored snapshot per the committed lock
-Refresh `<repo-root>/.apache-steward/` to a newer framework
-version, surface what changed, and reconcile any agentic
-overrides against the new framework structure.
+The upgrade flow is **drift-driven**. It detects mismatch
+between `<committed-lock>` (project pin) and `<local-lock>`
+(per-machine fetch), then re-installs per the committed lock,
+refreshes symlinks, and reconciles overrides.
+
+Two trigger paths land here:
+
+1. **User-initiated** — explicit `/setup-steward upgrade`,
+ e.g. after a colleague bumped `<committed-lock>` to a
+ new framework version and the user wants to align.
+2. **Drift-triggered from a framework skill** — any
+ framework skill (or `/setup-steward verify`) detected
+ drift on its pre-flight check and the user accepted the
+ proposal to upgrade.
+
+Both paths run the same flow.
## Inputs
-- `from:<git-ref>` — bring the snapshot to a specific framework
- ref (default: latest `main`).
-- `dry-run` — show what would change without modifying anything.
+- `from:<git-ref>` / `from:<version>` — bring the snapshot
+ to a specific framework ref **for this run only**. Does
+ NOT update `<committed-lock>` (use a project-level commit
+ for that). Useful for testing a candidate version before
+ pinning it.
+- `bump-committed` — also update `<committed-lock>` to the
+ new ref. Use when this run is the project-level decision
+ to move to a newer version (the diff lands in the user's
+ next commit).
+- `dry-run` — show what would change without modifying
+ anything.
## Step 0 — Pre-flight
-1. Read `<repo-root>/.apache-steward.lock` for the current
- pinned commit SHA. If missing, the repo isn't adopted —
- suggest `/setup-steward adopt` and stop.
-2. Read `<repo-root>/.apache-steward/` to confirm the snapshot
- is on disk. If missing (gitignored, fresh clone),
- re-download to the locked SHA first — that's the
- recover-snapshot path, not an upgrade. Then continue.
+1. Read `<committed-lock>`. If missing, the repo isn't
+ adopted — suggest `/setup-steward adopt` and stop.
+2. Read `<local-lock>`. If missing (gitignored, fresh
+ clone), the local install hasn't been initialised yet —
+ route as a recover-snapshot install per the committed
+ lock, not as an upgrade. Continue at Step 3.
-## Step 1 — Compare locked vs upstream
+## Step 1 — Compute drift
-Fetch upstream's latest SHA for the configured ref:
+Compare `<committed-lock>` to `<local-lock>` and to upstream
+where applicable:
-```bash
-git ls-remote https://github.com/apache/airflow-steward.git \
- refs/heads/main
-```
+| Method | Drift signal |
+|---|---|
+| `git-branch` | `<local-lock>.fetched_commit` vs upstream's current tip of `<committed-lock>.ref` (the branch). Drift if upstream has commits the local snapshot does not. |
+| `git-tag` | `<committed-lock>.ref` vs `<local-lock>.source_ref`. Drift if they differ. |
+| `svn-zip` | `<committed-lock>.ref` (version) vs `<local-lock>.source_ref`. Drift if they differ. Also: if `sha512` differs, surface as a security-flagged drift (the released zip changed under the same version — investigate). |
-If the locked SHA matches upstream, surface that and stop —
-the snapshot is up to date. The user can re-invoke later.
+Also check method change: if
+`<committed-lock>.method != <local-lock>.source_method`, the
+project switched install methods — surface as a drift that
+needs a full re-install.
-Otherwise, list the commits between locked and upstream
-(shallow log via the GitHub API or by re-cloning into a temp
-dir; both work).
+If no drift detected, surface and stop — the local snapshot
+matches the committed pin, no upgrade needed.
## Step 2 — Surface what changed
-Show the user:
+For each kind of drift, present:
-- The commit list (`git log --oneline <locked>..<upstream>`).
-- Files touched in the framework `.claude/skills/` directory,
- grouped by skill family. Call out any change to a skill the
- adopter has an override for (overrides may need
- reconciliation — see Step 4).
-- Any change to the `setup-steward` skill itself in the
- framework — that means the adopter's *committed* copy may
- have drifted. Surface as an extra note; the adopter chooses
- whether to re-copy.
+- **Commits between `<local>` and `<committed>`** — for
+ `git-branch` and `git-tag` methods, list the commit log
+ (`git log --oneline <local-commit>..<committed-commit>`)
+ via the GitHub API or by re-cloning to a temp dir.
+- **Files touched in the framework's `.claude/skills/`** —
+ grouped by skill family. Call out any change to a skill
+ the adopter has an override for (the override will need
+ reconciliation in Step 5).
+- **`setup-steward` skill changed in the framework** —
+ surface as a separate note. The adopter's *committed*
+ copy of `setup-steward` may need re-copying from the new
+ snapshot; that is an explicit project-level decision, not
+ auto-applied.
-Ask for explicit confirmation before refreshing.
+Ask for explicit confirmation before deleting and re-
+installing.
-## Step 3 — Refresh the snapshot
-
-Replace `<repo-root>/.apache-steward/` with a fresh
-`--depth=1` clone at the new ref:
+## Step 3 — Delete the old snapshot
```bash
rm -rf .apache-steward
-git clone --depth=1 \
- --branch <ref> \
- https://github.com/apache/airflow-steward.git \
- .apache-steward
```
-Update `.apache-steward.lock` with the new SHA + date.
+The snapshot is gitignored — destroying it loses no
+committed work. Do this **before** the new install to avoid
+"new layered on top of old" partial state.
-If the user is on a UNIX system with hardlink-aware tools, an
-optimization is to clone alongside and `mv` — but a simple
-nuke-and-clone is the canonical path and is what the skill
-defaults to. The snapshot is gitignored anyway, so destroying
-it loses no committed work.
+## Step 4 — Install per the committed lock
-## Step 4 — Reconcile overrides
+Per `<committed-lock>.method`:
-For each file in `<repo-root>/.apache-steward-overrides/`:
+- **`git-branch`** — `git clone --depth=1 --branch
+ <committed.ref> <committed.url> .apache-steward`. If
+ `from:<git-ref>` was passed, use that branch instead of
+ the committed one (this run only).
+- **`git-tag`** — `git clone --depth=1 --branch
+ <committed.ref> <committed.url> .apache-steward`. If
+ `from:<git-ref>` overrides, use it.
+- **`svn-zip`** — `curl` the zip + verify `sha512` +
+ `unzip` to `.apache-steward/`. The verification step is
+ **mandatory**; mismatched SHA-512 stops the upgrade and
+ surfaces the discrepancy.
+
+After install, capture the actual on-disk state for the
+new `<local-lock>`:
+
+- `source_method`, `source_url`, `source_ref` — from
+ whatever method was used in this run (committed lock or
+ `from:` override).
+- `fetched_commit` — `git -C .apache-steward rev-parse
+ HEAD` for git methods; the version for svn-zip.
+- `fetched_at` — current ISO-8601 timestamp.
-1. Check the corresponding framework skill exists in the new
- snapshot. If not (skill renamed or removed), surface as a
- conflict — the override may now apply to nothing. The user
- either updates the override's target skill name or removes
- the override.
-2. If the framework skill's structure changed in a way the
- override anchors against (e.g. the override invalidates
- "Step 5 — Land the valid/invalid consensus" but the
- framework renumbered or restructured steps), surface as a
- conflict. The user re-anchors the override against the new
- structure.
+## Step 5 — Reconcile overrides
-The skill **does not** auto-rewrite overrides. It surfaces
-conflicts and lets the user decide; agentic interpretation
-means the right call is human judgement, not pattern-matching.
+For each file in `<repo-root>/.apache-steward-overrides/`:
-## Step 5 — Re-create symlinks
+1. **Target skill check** — does the named framework skill
+ exist in the new snapshot? If not (skill renamed or
+ removed):
+ - Surface as conflict.
+ - The user updates the override's target skill name OR
+ deletes the override.
+2. **Anchor check** — if the override references framework
+ structure (step numbers, golden rules, decision-table
+ rows) that has changed in the new framework version:
+ - Surface as conflict, with the specific anchors that
+ have moved.
+ - The user re-anchors the override against the new
+ structure.
+
+The skill **does not** auto-rewrite overrides. Agentic
+interpretation means the right call is human judgement, not
+pattern-matching.
+
+## Step 6 — Refresh framework-skill symlinks
Walk `<adopter-skills-dir>` looking for stale symlinks that
point at framework skills no longer in the new snapshot
-(rename, removal). For each, ask the user to either:
+(rename, removal). For each:
-- Remove the stale symlink (renamed-away skill is gone), or
-- Re-symlink to the new name (if the framework documented a
- rename).
+- If renamed (the framework documented a rename in its
+ release notes), offer to re-symlink to the new name.
+- If removed, offer to remove the stale symlink.
Then walk the new snapshot for any new framework skills in
the families the adopter previously picked, and offer to
symlink them in.
-## Step 6 — Sanity check
+For the double-symlinked convention, refresh both layers.
+
+## Step 7 — Update `<local-lock>`
+
+Write the new local lock with the values captured in Step
+4.
+
+## Step 8 — (optional) Update `<committed-lock>`
+
+If `bump-committed` was passed, update
+`<committed-lock>.ref` (and per-method extras: `commit` for
+tag, `sha512` for svn-zip) to the new fetch. Surface the
+diff to the user before writing.
+
+Without `bump-committed`, the committed lock is unchanged —
+this upgrade is a *local* sync to the project pin.
+
+## Step 9 — Sanity check
Run [`verify.md`](verify.md)'s checklist as a final step.
## Output to the user
```text
-Snapshot refreshed: <old-SHA> → <new-SHA>
- X commits pulled (see list above)
- Y framework skills changed
- Z framework skills added
- W framework skills renamed/removed (see Step 5)
-
-Overrides reconciled:
- ✓ <list of overrides whose target skill is unchanged>
- ⚠ <list of overrides flagged for re-anchoring>
-
-.apache-steward.lock updated. Symlinks refreshed.
+Drift remediated:
+ Method: <method>
+ Project: <committed.ref> (committed)
+ Local: <local.fetched-commit-or-version> → <new>
+ Snapshot: refreshed at .apache-steward/
+
+Symlinks:
+ ✓ <list of unchanged symlinks>
+ + <list of newly-symlinked framework skills>
+ - <list of removed stale symlinks>
+
+Overrides:
+ ✓ <list of overrides whose target is unchanged>
+ ⚠ <list of overrides flagged for re-anchoring> (open the
+ file and update against the new framework structure)
Recommended follow-ups:
- Run /setup-isolated-setup-update if the secure-setup blast
radius (settings.json, agent-isolation/, pinned-versions.toml)
appears in the diff.
- - For each ⚠ override, open the file and re-anchor against the
- new framework structure.
+ - Open .apache-steward-overrides/<name>.md for any ⚠ entry above.
```
## Failure modes
-- **`.apache-steward.lock` missing** → repo not adopted yet;
- suggest `/setup-steward adopt`.
+- **`<committed-lock>` missing** → repo not adopted; suggest
+ `/setup-steward adopt`.
- **Network failure** → stop, surface error, user retries.
-- **Conflict during reconcile** → not a failure per se; the
- skill surfaces the conflict and finishes the upgrade up to
- the conflict. The user's next step is editing the override
- files.
+ The skill never leaves a half-deleted snapshot — Step 3's
+ `rm -rf` runs only after Step 2's user confirmation.
+- **SHA-512 mismatch on svn-zip** → stop. The released zip
+ has changed content under the same version, which is a
+ serious signal. The user investigates (re-check the
+ project's announcement, re-fetch the official KEYS,
+ consider whether the project re-released).
+- **Conflict during reconcile** → not a failure per se. The
+ skill surfaces the conflict and finishes the upgrade up
+ to Step 7. The user's next step is editing the override
+ file.
diff --git a/.claude/skills/setup-steward/verify.md b/.claude/skills/setup-steward/verify.md
index 3a12ec7..013218f 100644
--- a/.claude/skills/setup-steward/verify.md
+++ b/.claude/skills/setup-steward/verify.md
@@ -1,29 +1,29 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/legal/release-policy.html -->
-# verify — read-only health check of the steward integration
+# verify — health check of the steward integration + drift detection
Confirms the framework is wired in correctly so the rest of
-the framework's skills resolve from the right paths. Read-only
-— never modifies anything; surfaces gaps and remediation
-commands.
+the framework's skills resolve from the right paths, and
+surfaces any **drift** between the committed lock (project
+pin) and the local lock (per-machine fetch). Read-only by
+default — surfaces gaps and remediation commands.
## Inputs
- `--auto-fix-symlinks` — *exception to read-only*. If the
snapshot is present but symlinks are missing or dangling,
recreate them. Used by the post-checkout hook
- ([`adopt.md` Step 6](adopt.md)) on a fresh worktree where
- the gitignored symlinks didn't follow the checkout.
+ ([`adopt.md` Step 10](adopt.md)) on a fresh worktree
+ where the gitignored symlinks didn't follow the
+ checkout.
## Pre-flight
-1. `git rev-parse --show-toplevel` — must succeed; we need a
- repo root to resolve relative paths.
-2. Read `git remote get-url origin`. If it resolves to
- `apache/airflow-steward` (or a fork of), refuse — this
- skill is for repos that *adopt* the framework, not for
- the framework itself.
+1. `git rev-parse --show-toplevel` — must succeed.
+2. `git remote get-url origin` — refuse if it resolves to
+ `apache/airflow-steward` itself (this skill is for
+ adopters, not the framework).
3. If `<repo-root>/.apache-steward.lock` is missing, the
repo is not adopted. Surface and stop with a pointer at
`/setup-steward adopt`.
@@ -36,60 +36,91 @@ directory or doc updates — surface every check).
### 1. Snapshot present + intact
-`<repo-root>/.apache-steward/` exists, is a directory, and
-contains the expected top-level files (`README.md`,
-`AGENTS.md`, `.claude/skills/`, `tools/`).
+`<snapshot-dir>/` exists, is a directory, and contains the
+expected top-level files (`README.md`, `AGENTS.md`,
+`.claude/skills/`, `tools/`).
-- ✗ if missing → run `/setup-steward upgrade` to re-fetch
- (it gracefully handles the recover-snapshot case when the
- lock file exists but the snapshot doesn't).
-- ✗ if missing top-level files → snapshot is corrupted; same
- remediation.
+- ✗ if missing → run `/setup-steward upgrade` (it
+ gracefully handles the recover-snapshot case when the
+ committed lock exists but the snapshot does not).
+- ✗ if missing top-level files → snapshot is corrupted;
+ same remediation.
+
+### 2. Both lock files exist + parse
+
+`<committed-lock>` (`.apache-steward.lock`) and
+`<local-lock>` (`.apache-steward.local.lock`) both parse.
+
+- ✗ if `<committed-lock>` is missing → not adopted;
+ redirect (already caught in pre-flight).
+- ⚠ if `<local-lock>` is missing or unparsable → first
+ install on this machine has not run, or the file was
+ truncated. Suggest `/setup-steward upgrade` to re-create
+ the snapshot + write the local lock.
+
+### 3. Drift between committed and local locks
-### 2. Snapshot pinned to a real commit
+This is the **core drift check**. The same logic every
+framework skill runs at the top of its invocation.
-`<repo-root>/.apache-steward.lock` parses, the recorded
-`commit:` SHA matches `git -C .apache-steward rev-parse HEAD`.
+Compare:
-- ⚠ if mismatch — somebody modified the snapshot manually.
- Suggest `/setup-steward upgrade` to re-pin or `git -C
- .apache-steward checkout <locked-sha>` to revert.
+- `<committed-lock>.method` vs `<local-lock>.source_method`
+- `<committed-lock>.url` vs `<local-lock>.source_url`
+- `<committed-lock>.ref` vs `<local-lock>.source_ref`
+- For `git-branch`: also compare upstream tip (the actual
+ current `HEAD` of the branch on the remote) against
+ `<local-lock>.fetched_commit`.
-### 3. `.gitignore` correctly excludes the snapshot + symlinks
+| Result | Severity |
+|---|---|
+| All match (and for `git-branch`, local is at upstream tip) | ✓ |
+| Method or URL differ | ✗ — full re-install needed; remediation: `/setup-steward upgrade` |
+| Ref differs (e.g. project bumped tag, or `git-branch` local is behind upstream) | ⚠ — sync needed; remediation: `/setup-steward upgrade` |
+| `svn-zip` SHA-512 differs from the verification anchor in `<committed-lock>` | ✗ — security-flagged; the released zip changed content; investigate before upgrading |
+
+### 4. `.gitignore` correctly excludes the snapshot + local lock + symlinks
Check that the entries from
-[`adopt.md` Step 3](adopt.md) are present in
-`<repo-root>/.gitignore`. The snapshot path
-`/.apache-steward/` is **required**; the symlink patterns are
-**recommended** (otherwise a fresh clone may try to commit
-dangling symlinks).
+[`adopt.md` Step 7](adopt.md) are present in
+`<repo-root>/.gitignore`. Required:
+
+- `/.apache-steward/` (snapshot path)
+- `/.apache-steward.local.lock` (per-machine state)
+
+Recommended:
+
+- The framework-skill symlink patterns (`security-*`,
+ `pr-management-*`, `setup-isolated-setup-*`,
+ `setup-shared-config-sync`) under both `.claude/skills/`
+ and `.github/skills/` per convention.
- ✗ if `/.apache-steward/` is not gitignored — the snapshot
- is at risk of being accidentally committed; remediation:
- add the line.
+ is at risk of being accidentally committed.
+- ✗ if `/.apache-steward.local.lock` is not gitignored —
+ per-machine state would leak into the repo.
- ⚠ if symlink patterns are not gitignored.
-### 4. Symlinks point at live framework skills
+### 5. Symlinks point at live framework skills
For each symlink under `<adopter-skills-dir>` that resolves
into `.apache-steward/.claude/skills/<name>/`:
- ✓ if the target exists.
- ✗ if dangling (target deleted or snapshot missing).
- Remediation: `/setup-steward adopt` (idempotent re-run) or
- this same skill with `--auto-fix-symlinks`.
+ Remediation: `/setup-steward adopt` (idempotent re-run)
+ or this same skill with `--auto-fix-symlinks`.
-For each framework skill in the snapshot that is **not**
-symlinked in the adopter — surface as ⚠ with the family
-classification (`security`, `pr-management`, `setup`). The
-user may have intentionally not picked that family; the
-warning prompts a decision.
+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.
-### 5. `.apache-steward-overrides/` exists + has the README
+### 6. `.apache-steward-overrides/` exists + has the README
-`<repo-root>/.apache-steward-overrides/` is a directory with
-the `README.md` scaffold from
-[`adopt.md` Step 5](adopt.md).
+`<repo-root>/.apache-steward-overrides/` is a directory
+with the `README.md` scaffold from
+[`adopt.md` Step 9](adopt.md).
- ✗ if missing → `/setup-steward adopt` (idempotently
re-creates).
@@ -97,28 +128,30 @@ the `README.md` scaffold from
may have been hand-created. Suggest re-running
`/setup-steward adopt`.
-### 6. The `setup-steward` skill itself is up to date
+### 7. The `setup-steward` skill itself is up to date
Compare the adopter-side committed `setup-steward` skill
-against the snapshot's `.apache-steward/.claude/skills/setup-steward/`.
+(at `<adopter-skills-dir>/setup-steward/`) against the
+snapshot's `.apache-steward/.claude/skills/setup-steward/`.
- ✓ if same content.
-- ⚠ if different — the adopter's committed copy has drifted.
- Suggest re-copying from the snapshot. The user may have
- intentional local tweaks; surface as ⚠ not ✗.
+- ⚠ if different — the adopter's committed copy has
+ drifted. Suggest re-copying from the snapshot per the
+ install recipe. The user may have intentional local
+ tweaks; surface as ⚠ not ✗.
-### 7. Post-checkout hook installed
+### 8. Post-checkout hook installed
-`<repo-root>/.git/hooks/post-checkout` exists, is executable,
-and contains the `setup-steward verify --auto-fix-symlinks`
-recipe.
+`<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.
+ repo will need a manual
+ `/setup-steward verify --auto-fix-symlinks` after
+ checkout. Print the install recipe.
-### 8. Project documentation mentions the framework
+### 9. Project documentation mentions the framework
`<repo-root>/README.md` (or another committed doc the
adopter picked) mentions the steward adoption with a link
@@ -133,11 +166,16 @@ intentionally opted out of), say so explicitly and stop.
If anything is ✗, end the report with a concrete next-step
list, ordered most → least urgent:
-- ✗ on check 1 → `/setup-steward upgrade` (re-fetches).
-- ✗ on check 4 → `/setup-steward verify
- --auto-fix-symlinks` (cheap; no-op when symlinks already
- correct).
-- ✗ on check 5 → `/setup-steward adopt` (idempotent
+- ✗ on check 1 → `/setup-steward upgrade` (re-fetches per
+ the committed lock).
+- ✗ on check 3 (drift) → `/setup-steward upgrade`.
+- ✗ on check 5 (dangling symlinks) →
+ `/setup-steward verify --auto-fix-symlinks` (cheap;
+ no-op when symlinks already correct).
+- ✗ on check 6 → `/setup-steward adopt` (idempotent
re-create).
+- ✗ on check 4 / SHA-512 mismatch → **investigate first**;
+ do not run upgrade until you understand why the
+ released zip changed under the same version.
- All other ✗ / ⚠ → name the gap, give the one-line
remediation.
diff --git a/README.md b/README.md
index bb34b29..1423a45 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,10 @@
- [Apache Steward (to be renamed)](#apache-steward-to-be-renamed)
- [How adoption works](#how-adoption-works)
- [Adopting the framework](#adopting-the-framework)
+ - [1. Bootstrap (copy-pasteable shell)](#1-bootstrap-copy-pasteable-shell)
+ - [2. Skill takeover](#2-skill-takeover)
+ - [Subsequent contributors](#subsequent-contributors)
+ - [Drift detection](#drift-detection)
- [Skill families](#skill-families)
- [Maintenance](#maintenance)
- [Cross-references](#cross-references)
@@ -90,32 +94,87 @@ a gitignored snapshot, and agent-readable override files.
## Adopting the framework
-Tell your agent: **"adopt apache/airflow-steward in my repo"**.
+Two phases — a **shell bootstrap** that gets `setup-steward`
+into your repo, then the **skill takeover** that wires up the
+rest interactively.
-The agent should:
+### 1. Bootstrap (copy-pasteable shell)
-1. Read this README (you're here).
-2. Copy the
+Pick an install method and follow the verbatim recipe in
+[**`docs/setup/install-recipes.md`**](docs/setup/install-recipes.md):
+
+| Method | When to use | Reproducibility |
+|---|---|---|
+| `svn-zip` | Production once ASF official releases ship to `dist.apache.org` (signed + checksummed) | Frozen by version |
+| `git-tag` | Pin a specific framework version | Frozen by tag |
+| `git-branch` (default `main`) | WIP path — track the framework's `main` directly. The default during the framework's pre-release phase. | Tracks tip |
+
+Each recipe is a single shell block that:
+
+1. Adds `.apache-steward/`, `.apache-steward.local.lock`, and
+ the framework-skill symlinks to `.gitignore`.
+2. Downloads + verifies + extracts the framework into
+ `.apache-steward/` (gitignored — build artefact, not
+ source).
+3. Copies the
[`setup-steward`](.claude/skills/setup-steward/SKILL.md)
- skill from this framework into your repo's skill directory,
- matching your existing convention (flat `.claude/skills/` or
- the double-symlinked pattern — see
- [`conventions.md`](.claude/skills/setup-steward/conventions.md)).
- This is the **only** framework artefact the adopter commits.
-3. Invoke `/setup-steward` to do the rest:
-
- - download the snapshot into `.apache-steward/` (gitignored),
- - create symlinks in your skill directory for the families
- you pick (security and/or pr-management),
- - scaffold `.apache-steward-overrides/` (committed),
- - update your repo's `.gitignore`,
- - install a `post-checkout` git hook so worktrees re-create
- the gitignored symlinks automatically,
- - update your project documentation with a brief mention.
+ skill into your skills directory, matching your existing
+ convention (flat `.claude/skills/<n>/` or the
+ double-symlinked `.claude/skills/<n>` →
+ `.github/skills/<n>/` pattern).
+
+After the recipe completes, the framework snapshot is on
+disk and the bootstrap skill is in your repo.
+
+### 2. Skill takeover
+
+Tell your agent: **"adopt apache/airflow-steward in my repo"**
+(or invoke `/setup-steward` directly). The skill walks
+through the rest:
+
+- writes `.apache-steward.lock` (committed) — the project's
+ pin: install method + URL + ref + verification anchor;
+- writes `.apache-steward.local.lock` (gitignored) — what
+ this machine actually fetched + when;
+- asks which skill families (`security`, `pr-management`) to
+ symlink in;
+- creates the gitignored framework-skill symlinks;
+- scaffolds `.apache-steward-overrides/` (committed) for any
+ local workflow modifications;
+- installs a `post-checkout` git hook so worktrees re-create
+ runtime state automatically;
+- updates your project documentation with a brief mention.
After the skill finishes, you commit the small, focused
diff — the bootstrap skill, the `.gitignore` entries, the
-overrides scaffold, the doc note — and you're done. Open a PR.
+two lock files (committed + gitignore exclusion for the
+local one), the overrides scaffold, the doc note — and you're
+done. Open a PR.
+
+### Subsequent contributors
+
+Future contributors who clone your repo just say "adopt
+apache-steward in this repo" (or invoke `/setup-steward`).
+The skill reads `.apache-steward.lock` (already committed)
+and re-installs to the same version your project pinned. No
+need to redo the manual recipe — the committed lock is the
+project's source-of-truth.
+
+### Drift detection
+
+Every framework skill compares the gitignored
+`.apache-steward.local.lock` against the committed
+`.apache-steward.lock` at the top of its run. If they have
+drifted (project lead bumped the pin, or the local install
+is stale on a `main`-tracking adopter), the skill surfaces
+the gap and proposes `/setup-steward upgrade`. `upgrade`
+deletes the gitignored snapshot, re-installs per the
+committed pin, refreshes the gitignored symlinks, and
+reconciles any agentic overrides — see
+[`docs/setup/install-recipes.md`](docs/setup/install-recipes.md)
+and
+[`.claude/skills/setup-steward/upgrade.md`](.claude/skills/setup-steward/upgrade.md)
+for the full flow.
## Skill families
diff --git a/docs/setup/install-recipes.md b/docs/setup/install-recipes.md
new file mode 100644
index 0000000..4e2c6b7
--- /dev/null
+++ b/docs/setup/install-recipes.md
@@ -0,0 +1,247 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [Install recipes — bootstrap apache-steward in an adopter repo](#install-recipes--bootstrap-apache-steward-in-an-adopter-repo)
+ - [Method 1 — released zip from ASF distribution](#method-1--released-zip-from-asf-distribution)
+ - [Method 2 — git tag](#method-2--git-tag)
+ - [Method 3 — git branch (defaults to `main`)](#method-3--git-branch-defaults-to-main)
+ - [After any recipe — let the skill take over](#after-any-recipe--let-the-skill-take-over)
+ - [Subsequent runs and drift detection](#subsequent-runs-and-drift-detection)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+<!-- SPDX-License-Identifier: Apache-2.0
+ https://www.apache.org/legal/release-policy.html -->
+
+# Install recipes — bootstrap apache-steward in an adopter repo
+
+Three copy-pasteable shell recipes for fetching the framework
+into a new adopter project's repo. Each recipe is **the
+bootstrap that gets `setup-steward` into the repo**; once it is
+in place, the rest of the adoption (skill-family pick, framework
+symlinks, project doc note, gitignored runtime state) runs
+through `/setup-steward` interactively.
+
+Pick the recipe that matches your distribution preference:
+
+| Method | When to use | Reproducibility |
+|---|---|---|
+| [**svn-zip**](#method-1--released-zip-from-asf-distribution) | Production adopters once the framework ships official ASF releases. Signed + checksummed. | Frozen by version |
+| [**git-tag**](#method-2--git-tag) | Pinning a specific framework version (e.g. for testing a release candidate, or for a cautious adopter who tracks named releases only). | Frozen by tag |
+| [**git-branch**](#method-3--git-branch-defaults-to-main) | WIP path — track the framework's `main` branch directly. The default during the framework's pre-release phase. | Tracks branch tip |
+
+> **Adopter convention** — pick the right `cp` step per your
+> existing skills layout:
+>
+> - **flat** (`.claude/skills/<n>/SKILL.md` directly): copy
+> `setup-steward` into `.claude/skills/setup-steward/`.
+> - **double-symlinked** (e.g. `apache/airflow` —
+> `.claude/skills/<n>` → `.github/skills/<n>/`): copy the
+> content into `.github/skills/setup-steward/` AND symlink
+> `.claude/skills/setup-steward` → `../../.github/skills/setup-steward`.
+>
+> The `setup-steward` skill itself is the **only** framework
+> artefact you commit. Every other framework skill is wired
+> in by the `setup-steward adopt` flow as gitignored symlinks
+> into the gitignored snapshot.
+
+---
+
+## Method 1 — released zip from ASF distribution
+
+> **Status: forthcoming.** ASF release distribution
+> (`https://dist.apache.org/repos/dist/release/<project>/`)
+> is the canonical home for ASF-blessed releases per the
+> [release-policy](https://www.apache.org/legal/release-policy.html)
+> and [infra release-distribution guidelines](https://infra.apache.org/release-distribution.html).
+> This recipe will be the recommended path once the framework
+> ships its first official release; until then, use
+> [Method 3 — git-branch](#method-3--git-branch-defaults-to-main).
+
+```bash
+# === apache-steward bootstrap — Method 1: signed zip from ASF dist ===
+# Replace <PROJECT> with the host adopter's ASF dist subdirectory
+# (e.g. `airflow` once releases land at
+# https://dist.apache.org/repos/dist/release/airflow/).
+# Replace <VERSION> with the framework version you want.
+
+cd /path/to/your/repo
+
+VERSION=<VERSION>
+PROJECT=<PROJECT>
+DIST_BASE=https://dist.apache.org/repos/dist/release/${PROJECT}
+ZIP=apache-steward-${VERSION}-source-release.zip
+
+# 1. Download zip + signature + checksum, verify, extract to .apache-steward/
+curl -fsSLO ${DIST_BASE}/${ZIP}
+curl -fsSLO ${DIST_BASE}/${ZIP}.sha512
+curl -fsSLO ${DIST_BASE}/${ZIP}.asc
+sha512sum -c ${ZIP}.sha512
+# Optional but recommended — verify the OpenPGP signature against the
+# project KEYS file (see https://infra.apache.org/release-signing.html):
+# curl -fsSLO ${DIST_BASE}/KEYS
+# gpg --import KEYS
+# gpg --verify ${ZIP}.asc ${ZIP}
+
+mkdir -p .apache-steward
+unzip -q ${ZIP} -d .apache-steward
+mv .apache-steward/apache-steward-${VERSION}/* \
+ .apache-steward/apache-steward-${VERSION}/.[!.]* \
+ .apache-steward/ 2>/dev/null
+rmdir .apache-steward/apache-steward-${VERSION}
+rm -f ${ZIP} ${ZIP}.sha512 ${ZIP}.asc
+
+# 2. Copy the `setup-steward` skill into your skills dir.
+# Pick ONE branch based on your existing convention.
+#
+# A — flat layout (default):
+cp -r .apache-steward/.claude/skills/setup-steward .claude/skills/setup-steward
+#
+# B — double-symlinked layout (e.g. apache/airflow):
+# mkdir -p .github/skills .claude/skills
+# cp -r .apache-steward/.claude/skills/setup-steward .github/skills/setup-steward
+# ln -sf ../../.github/skills/setup-steward .claude/skills/setup-steward
+
+# 3. Add gitignore entries (idempotent — re-run is safe)
+cat >> .gitignore <<'GITIGNORE'
+
+# apache-steward — gitignored snapshot of the framework, refreshed
+# by /setup-steward upgrade. Build artefact, not source.
+/.apache-steward/
+
+# Per-machine local-pin file. Records what THIS machine fetched and
+# when. Compared against the committed .apache-steward.lock to
+# detect drift.
+/.apache-steward.local.lock
+
+# Symlinks created by /setup-steward into the gitignored snapshot.
+/.claude/skills/security-*
+/.claude/skills/pr-management-*
+/.claude/skills/setup-isolated-setup-*
+/.claude/skills/setup-shared-config-sync
+# Mirror the same patterns under .github/skills/ if your repo uses
+# the double-symlinked convention.
+/.github/skills/security-*
+/.github/skills/pr-management-*
+/.github/skills/setup-isolated-setup-*
+/.github/skills/setup-shared-config-sync
+GITIGNORE
+
+# 4. Tell your agent: "follow /setup-steward to finish adopting steward."
+# The skill will write .apache-steward.lock (committed) and
+# .apache-steward.local.lock (gitignored), ask which skill family
+# to wire up, create the gitignored framework-skill symlinks, and
+# update your project docs.
+```
+
+---
+
+## Method 2 — git tag
+
+```bash
+# === apache-steward bootstrap — Method 2: pinned git tag ===
+# Replace <TAG> with the framework tag you want
+# (e.g. `v1.0.0` once tags exist on apache/airflow-steward).
+
+cd /path/to/your/repo
+
+TAG=<TAG>
+git clone --depth=1 \
+ --branch ${TAG} \
+ https://github.com/apache/airflow-steward.git \
+ .apache-steward
+
+# Copy the `setup-steward` skill — pick A or B (see Method 1 step 2)
+cp -r .apache-steward/.claude/skills/setup-steward .claude/skills/setup-steward
+# OR for double-symlinked:
+# cp -r .apache-steward/.claude/skills/setup-steward .github/skills/setup-steward
+# ln -sf ../../.github/skills/setup-steward .claude/skills/setup-steward
+
+# Add gitignore entries (same block as Method 1 step 3 — see there)
+
+# Tell your agent: "follow /setup-steward to finish adopting steward."
+```
+
+---
+
+## Method 3 — git branch (defaults to `main`)
+
+The default WIP path while the framework is pre-release.
+
+```bash
+# === apache-steward bootstrap — Method 3: git branch (default: main) ===
+cd /path/to/your/repo
+
+BRANCH=main # or another branch you want to track
+git clone --depth=1 \
+ --branch ${BRANCH} \
+ https://github.com/apache/airflow-steward.git \
+ .apache-steward
+
+# Copy the `setup-steward` skill — pick A or B (see Method 1 step 2)
+cp -r .apache-steward/.claude/skills/setup-steward .claude/skills/setup-steward
+# OR for double-symlinked:
+# cp -r .apache-steward/.claude/skills/setup-steward .github/skills/setup-steward
+# ln -sf ../../.github/skills/setup-steward .claude/skills/setup-steward
+
+# Add gitignore entries (same block as Method 1 step 3 — see there)
+
+# Tell your agent: "follow /setup-steward to finish adopting steward."
+```
+
+---
+
+## After any recipe — let the skill take over
+
+Once the recipe completes, `setup-steward` is in your repo and
+the snapshot is on disk (gitignored). Tell your agent:
+
+```text
+follow .claude/skills/setup-steward to adopt steward
+```
+
+(or invoke `/setup-steward` directly). The skill walks through
+the rest:
+
+1. **Pick the skill families** to symlink in (`security`,
+ `pr-management`).
+2. **Write the lock files**:
+ - `.apache-steward.lock` (**committed**) — the project's pin
+ (the method + URL + ref you used in the recipe). Future
+ adopters of *this same repo* re-install per this pin.
+ - `.apache-steward.local.lock` (**gitignored**) — what THIS
+ machine actually fetched (commit SHA, timestamp).
+3. **Create the symlinks** for chosen skill families
+ (gitignored — they target the gitignored snapshot).
+4. **Scaffold `.apache-steward-overrides/`** (committed) for
+ any local workflow modifications.
+5. **Install a `post-checkout` git hook** so worktrees
+ re-create the gitignored runtime state.
+6. **Update your project documentation** with a brief mention
+ of the framework adoption.
+
+After this, adopters fresh-cloning the repo can run
+`/setup-steward` and get the framework provisioned per your
+project's committed `.apache-steward.lock` — no need to redo
+the manual recipe.
+
+## Subsequent runs and drift detection
+
+Every framework skill — and `/setup-steward verify` —
+compares the local lock against the committed lock at the top
+of its run. If they have drifted (e.g. the project lead bumped
+`.apache-steward.lock` to a newer ref, or the local install is
+stale on a `main`-tracking adopter), the skill surfaces the
+gap and proposes:
+
+```text
+/setup-steward upgrade
+```
+
+`upgrade` deletes the gitignored snapshot, re-installs per the
+committed lock, refreshes the gitignored symlinks (adding any
+new framework skills, removing any that were renamed away),
+and updates the local lock. See
+[`setup-steward/upgrade.md`](../../.claude/skills/setup-steward/upgrade.md)
+for the full flow.