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 766a720  feat(setup-steward): main-only adoption + worktree-init 
sub-action (#136)
766a720 is described below

commit 766a72026b47d601ae7f186f898408eb208f55c3
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue May 12 20:22:07 2026 +0200

    feat(setup-steward): main-only adoption + worktree-init sub-action (#136)
    
    * feat(setup-steward): main-only adoption + worktree-init sub-action
    
    Today's adoption story: each adopter worktree gets its own
    gitignored \`.apache-steward/\` snapshot, so \`adopt\` / \`upgrade\`
    run independently in every worktree. That wastes disk, splits
    the upgrade source of truth, and makes worktrees drift out of
    step over time.
    
    New story: every worktree of an adopter shares **one** snapshot —
    the main checkout's. Adopt + upgrade only run in the main; each
    worktree's \`.apache-steward/\` is a symlink to the main's.
    
    Three file changes + one new file:
    
    1. \`setup-steward/SKILL.md\` — frontmatter sub-action list +
       in-skill table grow a \`worktree-init\` entry; \`adopt\`,
       \`upgrade\`, \`unadopt\` are now flagged main-checkout-only.
    
    2. \`setup-steward/adopt.md\` — Step 0 grows a worktree-refusal
       that points the operator at the main with an explicit
       \`cd <main-path>\` hint, and at \`/setup-steward worktree-init\`
       for the subsequent worktree wiring.
    
    3. \`setup-steward/upgrade.md\` — same Step 0 worktree-refusal;
       explains that worktrees pick up upgrades automatically via
       the symlink (no per-worktree upgrade needed).
    
    4. \`setup-steward/unadopt.md\` — same Step 0 worktree-refusal;
       notes that unadopting from a worktree would leave the main
       and other worktrees in a half-removed state.
    
    5. \`setup-steward/verify.md\` — Snapshot-present check grows two
       new branches: worktree + missing → recommend
       \`/setup-steward worktree-init\`; worktree + regular directory
       → recommend \`worktree-init\` with the move-aside flow.
    
    6. \`setup-steward/worktree-init.md\` (new) — the full recipe.
       Validates we are in a worktree, validates the main is adopted,
       inspects the worktree's existing \`<snapshot-dir>\` state (four
       cases, handled per-case), creates the symlink, verifies the
       chain end-to-end. Idempotent. The skill does **not** touch
       \`.apache-steward-overrides/\` — that directory is committed
       in the tracker repo and is worktree-local by design.
    
    Doc-only change. No runtime dependency: adopters who do not run
    worktree-init keep working exactly as before, with per-worktree
    snapshots; adopters who switch get the symlinked shape on next
    worktree creation.
    
    Generated-by: Claude Code (Opus 4.7)
    
    * ci: trim setup-steward SKILL.md frontmatter under 1536-char cap
    
    The worktree-init sub-action entry pushed description+when_to_use
    to 1579 chars (43 over the Claude-Code truncation limit). Compress
    the entry from three lines to two — the "shares one framework
    state" tail repeats what "symlink a worktree's snapshot to the
    main's" already says.
---
 .claude/skills/setup-steward/SKILL.md         |  29 +++++--
 .claude/skills/setup-steward/adopt.md         |  19 +++-
 .claude/skills/setup-steward/unadopt.md       |  18 +++-
 .claude/skills/setup-steward/upgrade.md       |  18 +++-
 .claude/skills/setup-steward/verify.md        |  33 +++++--
 .claude/skills/setup-steward/worktree-init.md | 119 ++++++++++++++++++++++++++
 6 files changed, 214 insertions(+), 22 deletions(-)

diff --git a/.claude/skills/setup-steward/SKILL.md 
b/.claude/skills/setup-steward/SKILL.md
index 0545b6c..579b334 100644
--- a/.claude/skills/setup-steward/SKILL.md
+++ b/.claude/skills/setup-steward/SKILL.md
@@ -6,9 +6,13 @@ description: |
   framework skill committed in an adopter's repo — every other
   skill is a symlink the adopt sub-action wires up.
   Sub-actions:
-    `/setup-steward`         — first-time adoption (default)
+    `/setup-steward`         — first-time adoption (default;
+                                main-checkout only)
     `/setup-steward upgrade` — refresh the gitignored snapshot
                                 per the committed lock
+                                (main-checkout only)
+    `/setup-steward worktree-init` — symlink a worktree's
+                                snapshot to the main's
     `/setup-steward verify`  — health check + drift detection
     `/setup-steward override <skill>` — open or scaffold an
                                 agentic override in
@@ -18,6 +22,7 @@ description: |
                                sections, this skill itself);
                                preserves `.apache-steward-
                                overrides/` by default
+                               (main-checkout only)
 when_to_use: |
   Invoke when the user says "adopt apache-steward", "adopt
   apache/airflow-steward", "set up steward in this repo",
@@ -25,7 +30,7 @@ when_to_use: |
   framework's README adoption instructions. Also for periodic
   maintenance: "upgrade steward", "verify steward setup",
   "check steward drift", "the snapshot is stale".
-argument-hint: "[adopt|upgrade|verify|override skill-name|unadopt]"
+argument-hint: "[adopt|upgrade|worktree-init|verify|override 
skill-name|unadopt]"
 license: Apache-2.0
 ---
 
@@ -238,12 +243,22 @@ The skill dispatches by the first positional argument:
 
 | Invocation | Loads | Purpose |
 |---|---|---|
-| `/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 per 
`<committed-lock>` + reconcile overrides + refresh symlinks. |
-| `/setup-steward verify` | [`verify.md`](verify.md) | Read-only health check 
+ drift status report. |
+| `/setup-steward` (no args) | [`adopt.md`](adopt.md) | First-time adoption 
(default; **main-checkout only**). Idempotent — re-running on an 
already-adopted repo behaves like `verify`. |
+| `/setup-steward adopt` | [`adopt.md`](adopt.md) | Same as no-arg — explicit 
form. Main-checkout only. |
+| `/setup-steward upgrade` | [`upgrade.md`](upgrade.md) | Refresh snapshot per 
`<committed-lock>` + reconcile overrides + refresh symlinks. **Main-checkout 
only** — worktrees pick up upgrades automatically via the symlink installed by 
`worktree-init`. |
+| `/setup-steward worktree-init` | [`worktree-init.md`](worktree-init.md) | 
**Worktree-only.** Symlink the worktree's `<snapshot-dir>` to the main 
checkout's so this worktree shares one framework state. No fetch, no lock files 
written; idempotent. |
+| `/setup-steward verify` | [`verify.md`](verify.md) | Read-only health check 
+ drift status report. Works in both main and worktrees. |
 | `/setup-steward override <skill>` | [`overrides.md`](overrides.md) | Open / 
scaffold an override file. |
-| `/setup-steward unadopt` | [`unadopt.md`](unadopt.md) | Reverse the 
adoption. Removes snapshot, locks, symlinks, hook, doc sections, and this skill 
itself. Preserves `.apache-steward-overrides/` unless `--purge-overrides` is 
passed. |
+| `/setup-steward unadopt` | [`unadopt.md`](unadopt.md) | Reverse the 
adoption. Removes snapshot, locks, symlinks, hook, doc sections, and this skill 
itself. Preserves `.apache-steward-overrides/` unless `--purge-overrides` is 
passed. **Main-checkout only.** |
+
+**Main-checkout-only sub-actions** (`adopt`, `upgrade`, `unadopt`)
+detect their context via `git rev-parse --git-dir` ≠
+`git rev-parse --git-common-dir` and refuse to run in a worktree
+with a pointer back to the main checkout. The worktree counterpart
+of `adopt` is `worktree-init`; for `upgrade`, every worktree
+automatically sees the refreshed snapshot once the main runs
+upgrade, because each worktree's `<snapshot-dir>` is a symlink to
+the main's.
 
 If the snapshot is missing (no `<snapshot-dir>/`) and
 `<committed-lock>` exists, the skill treats any sub-action as
diff --git a/.claude/skills/setup-steward/adopt.md 
b/.claude/skills/setup-steward/adopt.md
index e931ec3..01fcc94 100644
--- a/.claude/skills/setup-steward/adopt.md
+++ b/.claude/skills/setup-steward/adopt.md
@@ -42,10 +42,25 @@ between automatically:
 
 1. Confirm we are in a git repo (`git rev-parse
    --show-toplevel`).
-2. Confirm we are **not** in `apache/airflow-steward` itself
+2. **Confirm we are in the main checkout, not a git worktree.**
+   Compare `git rev-parse --git-dir` against
+   `git rev-parse --git-common-dir` — they are equal in the
+   main checkout and different in a worktree. If different,
+   stop with:
+
+   > *"`adopt` runs in the main checkout, not a worktree. From
+   > the main: `cd <main-path> && /setup-steward`. To wire this
+   > worktree up after adoption lands in the main, use
+   > `/setup-steward worktree-init`."*
+
+   The main's path is
+   `$(dirname "$(cd "$(git rev-parse --git-common-dir)" && pwd)")` —
+   surface it explicitly in the error message so the operator
+   can `cd` there without guessing.
+3. Confirm we are **not** in `apache/airflow-steward` itself
    (read `git remote get-url origin` and refuse if it
    resolves to the framework).
-3. Detect the adopter's existing skills-dir convention by
+4. Detect the adopter's existing skills-dir convention by
    following [`conventions.md`](conventions.md). Pin the
    result as `<adopter-skills-dir>` for the rest of this
    flow.
diff --git a/.claude/skills/setup-steward/unadopt.md 
b/.claude/skills/setup-steward/unadopt.md
index 0a53f75..e3ec70b 100644
--- a/.claude/skills/setup-steward/unadopt.md
+++ b/.claude/skills/setup-steward/unadopt.md
@@ -49,16 +49,28 @@ relevant override file rather than unadopting.
 
 1. Confirm we are in a git repo
    (`git rev-parse --show-toplevel`).
-2. Confirm we are **not** in `apache/airflow-steward` itself
+2. **Confirm we are in the main checkout, not a git worktree.**
+   Compare `git rev-parse --git-dir` against
+   `git rev-parse --git-common-dir`. If different, stop with:
+
+   > *"`unadopt` runs in the main checkout, not a worktree.
+   > Unadoption removes the shared snapshot every worktree
+   > points at; running from a worktree would leave the main
+   > and other worktrees in a half-removed state. From the
+   > main: `cd <main-path> && /setup-steward unadopt`. To
+   > undo just this worktree's symlink without touching the
+   > main, `rm <worktree>/.apache-steward` manually."*
+
+3. Confirm we are **not** in `apache/airflow-steward` itself
    (`git remote get-url origin`); refuse if it resolves to the
    framework — the framework is not "adopted into" itself.
-3. Confirm `<committed-lock>` (`.apache-steward.lock`) is
+4. Confirm `<committed-lock>` (`.apache-steward.lock`) is
    present. If missing, the repo is not adopted — surface and
    stop. (If only the snapshot is present without a committed
    lock, the adopter ran the install recipe but never
    completed `/setup-steward adopt`; treat that as not-yet-
    adopted and stop with the same message.)
-4. Detect the adopter's skills-dir convention per
+5. Detect the adopter's skills-dir convention per
    [`conventions.md`](conventions.md). Pin the result as
    `<adopter-skills-dir>` for the rest of this flow.
 
diff --git a/.claude/skills/setup-steward/upgrade.md 
b/.claude/skills/setup-steward/upgrade.md
index c0d6a5d..dc11b48 100644
--- a/.claude/skills/setup-steward/upgrade.md
+++ b/.claude/skills/setup-steward/upgrade.md
@@ -36,9 +36,23 @@ Both paths run the same flow.
 
 ## Step 0 — Pre-flight
 
-1. Read `<committed-lock>`. If missing, the repo isn't
+1. **Confirm we are in the main checkout, not a git worktree.**
+   Compare `git rev-parse --git-dir` against
+   `git rev-parse --git-common-dir`. If different, stop with:
+
+   > *"`upgrade` runs in the main checkout, not a worktree.
+   > From the main: `cd <main-path> && /setup-steward upgrade`.
+   > Every worktree automatically picks up the refreshed
+   > snapshot once the main upgrade lands, because each
+   > worktree's `<snapshot-dir>` is a symlink to the main's
+   > (per [`worktree-init.md`](worktree-init.md))."*
+
+   `<main-path>` resolves to
+   `$(dirname "$(cd "$(git rev-parse --git-common-dir)" && pwd)")` —
+   surface it explicitly so the operator can `cd` there.
+2. Read `<committed-lock>`. If missing, the repo isn't
    adopted — suggest `/setup-steward adopt` and stop.
-2. Read `<local-lock>`. If missing (gitignored, fresh
+3. 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.
diff --git a/.claude/skills/setup-steward/verify.md 
b/.claude/skills/setup-steward/verify.md
index e9bfc21..32a59bb 100644
--- a/.claude/skills/setup-steward/verify.md
+++ b/.claude/skills/setup-steward/verify.md
@@ -36,15 +36,32 @@ directory or doc updates — surface every check).
 
 ### 1. Snapshot present + intact
 
-`<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` (it
-  gracefully handles the recover-snapshot case when the
-  committed lock exists but the snapshot does not).
+`<snapshot-dir>/` exists (as a directory or a symlink that
+resolves to one) and contains the expected top-level files
+(`README.md`, `AGENTS.md`, `.claude/skills/`, `tools/`).
+
+- ✗ if missing **and we are in the main checkout** (`git
+  rev-parse --git-dir` equals `git rev-parse --git-common-dir`)
+  → run `/setup-steward upgrade` (it gracefully handles the
+  recover-snapshot case when the committed lock exists but
+  the snapshot does not).
+- ✗ if missing **and we are in a worktree** (the two dirs
+  differ) → run `/setup-steward worktree-init` to symlink
+  `<snapshot-dir>` to the main checkout's. Do **not**
+  propose `upgrade` — that creates a per-worktree snapshot,
+  which is the bug `worktree-init` is designed to prevent.
+- ⚠ if present as a regular directory **in a worktree** →
+  legacy per-worktree snapshot. Suggest
+  `/setup-steward worktree-init` (with the move-aside flow)
+  to convert into a symlink to the main's snapshot. Verify
+  continues — the per-worktree snapshot is still functional,
+  just wasteful.
 - ✗ if missing top-level files → snapshot is corrupted;
-  same remediation.
+  same remediation as the missing-snapshot case above.
+- ⚠ if `<snapshot-dir>` is a symlink that resolves outside
+  the same repo's main checkout — the operator pointed it
+  at a different framework checkout deliberately. Surface
+  the resolved target and continue; do not auto-remediate.
 
 ### 2. Both lock files exist + parse
 
diff --git a/.claude/skills/setup-steward/worktree-init.md 
b/.claude/skills/setup-steward/worktree-init.md
new file mode 100644
index 0000000..a82c1f6
--- /dev/null
+++ b/.claude/skills/setup-steward/worktree-init.md
@@ -0,0 +1,119 @@
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/legal/release-policy.html -->
+
+# worktree-init — share the main checkout's snapshot from a worktree
+
+`adopt` and `upgrade` are **main-checkout-only**. A new git
+worktree of an already-adopted tracker repo gets the framework
+state by **symlinking** its `.apache-steward/` directory to the
+main checkout's snapshot, rather than maintaining its own copy.
+One snapshot on disk, one upgrade source, every worktree always
+current.
+
+This sub-action is the worktree counterpart of `adopt`:
+
+- **`adopt`** runs in the main checkout, fetches the snapshot,
+  writes both lock files, and wires up symlinks.
+- **`worktree-init`** runs in a worktree, validates the main is
+  adopted, and points the worktree's `<snapshot-dir>` at the
+  main's. Nothing is fetched; no lock files are written.
+
+The skill is idempotent: re-running on a worktree that already
+has the right symlink is a no-op.
+
+## Step 0 — Pre-flight
+
+1. **Confirm we are in a git worktree, not the main checkout.**
+   Compare `git rev-parse --git-dir` against
+   `git rev-parse --git-common-dir`. They are equal in the main
+   checkout and different in a worktree. If equal, stop:
+
+   > *"You appear to be in the main checkout (`<path>`).
+   > `worktree-init` only runs in a worktree. Use
+   > `/setup-steward` (or `/setup-steward upgrade`) here
+   > instead."*
+
+2. **Resolve the main checkout's path.** Take
+   `$(cd "$(git rev-parse --git-common-dir)" && pwd)`; the
+   parent of that is the main checkout's working tree. Pin
+   the result as `<main>` for the rest of this flow.
+
+3. **Confirm the main checkout is adopted.** Check that
+   `<main>/.apache-steward/` exists and that
+   `<main>/.apache-steward.lock` exists. If either is missing,
+   stop:
+
+   > *"The main checkout at `<main>` is not adopted yet. From
+   > the main checkout: `cd <main> && /setup-steward`. Re-run
+   > `worktree-init` here once that is complete."*
+
+4. **Inspect the worktree's `<snapshot-dir>` state.** Four
+   possibilities, each handled below:
+
+   | Current state | Action |
+   |---|---|
+   | Missing | Step 1 — create the symlink. |
+   | Symlink to `<main>/.apache-steward/` | No-op. Surface "already wired" and 
stop. |
+   | 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
+
+```bash
+ln -s <main>/.apache-steward <worktree>/.apache-steward
+```
+
+Then verify the chain end-to-end:
+
+- `ls -la <worktree>/.apache-steward` returns a symlink pointing
+  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 2 — Recap
+
+Print a short summary:
+
+- The symlink that was just created.
+- The main checkout's resolved path.
+- The framework version the main is pinned at (read from
+  `<main>/.apache-steward.lock`).
+- A reminder: `upgrade` from the main, not from the worktree.
+
+## Inputs
+
+| Flag | Effect |
+|---|---|
+| `--force` | Replace an existing `<snapshot-dir>` (symlink or regular dir) 
without the confirmation prompt. Skips the move-aside backup. Use only when you 
are sure the existing state holds nothing worth keeping. |
+| `dry-run` | Show what the skill would do without writing anything. |
+
+## Adopter overrides
+
+This sub-action does **not** touch `.apache-steward-overrides/`.
+That directory is committed in the tracker repo and is
+worktree-local by design — different branches may carry
+different overrides. Symlinking it would conflate branches.
+
+## What this sub-action is NOT for
+
+- **Fetching the framework.** Use `adopt` in the main checkout
+  first.
+- **Upgrading the framework version.** Use `upgrade` in the
+  main checkout; the symlink means every worktree sees the
+  refreshed snapshot immediately.
+- **Auto-running on `git worktree add`.** Adopters who want
+  automatic worktree initialisation can wrap `git worktree add`
+  with a script that calls `/setup-steward worktree-init` —
+  the framework does not install that wrapper.
+
+## Failure modes
+
+| Symptom | Likely cause | Fix |
+|---|---|---|
+| Step 0 step 3 stops with "main checkout not adopted" | The main has never 
run `adopt`. | `cd <main> && /setup-steward`, then re-run `worktree-init` here. 
|
+| `worktree-init` runs but skills still fail to resolve | The 
`<adopter-skills-dir>/<skill>` symlinks are missing from this worktree's commit 
(the worktree was branched from before `adopt` ran on main). | Re-run 
`worktree-init` from main's `adopt` flow afterwards, or `git merge` / `git 
rebase` the branch carrying the symlink commits. |
+| `<snapshot-dir>` is a regular directory and `--force` is not passed | A 
previous worktree snapshot is still on disk. | Re-run the skill, accept the 
move-aside prompt, then optionally inspect `.apache-steward.bak.<timestamp>` 
for any non-snapshot content before deleting. |

Reply via email to