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.git
The following commit(s) were added to refs/heads/main by this push:
new 942de38a350 Make .agents/skills the canonical agent-skills home
(#68143)
942de38a350 is described below
commit 942de38a35017a8ada6b828d5b7d7ab5b4bc251f
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sun Jun 7 10:07:34 2026 +0200
Make .agents/skills the canonical agent-skills home (#68143)
Adopt the apache-steward #460 "canonical .agents/skills, relay other
agents into it" model and move every agent skill — the repo's own and
the magpie framework ones — to a single canonical home so they are
discoverable by the whole shared-path agent cluster (Codex, Cursor,
Gemini CLI, Copilot, OpenCode, …) alongside Claude Code and GitHub:
- `.agents/skills/<skill>` is the canonical entry; `.github/skills/`
and `.claude/skills/` carry per-skill relay symlinks pointing into it.
- Relocate the repo's own skills (`aip-user-stories`,
`airflow-translations`, `prepare-providers-documentation`) and the
committed `magpie-setup` bootstrap copy from `.github/skills/` to
`.agents/skills/`, with relays from `.github`/`.claude`.
- Refresh the committed `magpie-setup` bootstrap from
apache/airflow-steward main (the #460 canonical-.agents model;
`conventions.md` merged into `agents.md`).
Re-point the prek hooks and tooling that anchored on `.github/skills/...`
at the new `.agents/` location — full/short license headers,
blacken-docs, codespell, inclusive-language, markdownlint, lychee, the
translation-namespace sync trigger, and the hardcoded SKILL.md path in
`sync_translation_namespaces.py`. prek skips symlinks, so only the real
files (now under `.agents/`) are processed. Add `.gitignore` negations
so the `.claude/skills/` relays stay tracked, and update the
provider-release doc path reference.
---
.../skills/aip-user-stories/SKILL.md | 0
.../references/playbook-template.md | 0
.../skills/airflow-translations/SKILL.md | 0
.../skills/airflow-translations/locales/ar.md | 0
.../skills/airflow-translations/locales/ca.md | 0
.../skills/airflow-translations/locales/de.md | 0
.../skills/airflow-translations/locales/el.md | 0
.../skills/airflow-translations/locales/es.md | 0
.../skills/airflow-translations/locales/fr.md | 0
.../skills/airflow-translations/locales/he.md | 0
.../skills/airflow-translations/locales/hi.md | 0
.../skills/airflow-translations/locales/hu.md | 0
.../skills/airflow-translations/locales/it.md | 0
.../skills/airflow-translations/locales/ja.md | 0
.../skills/airflow-translations/locales/ko.md | 0
.../skills/airflow-translations/locales/nl.md | 0
.../skills/airflow-translations/locales/pl.md | 0
.../skills/airflow-translations/locales/pt.md | 0
.../skills/airflow-translations/locales/th.md | 0
.../skills/airflow-translations/locales/tr.md | 0
.../skills/airflow-translations/locales/zh-CN.md | 0
.../skills/airflow-translations/locales/zh-TW.md | 0
{.github => .agents}/skills/magpie-setup/SKILL.md | 83 +++---
{.github => .agents}/skills/magpie-setup/adopt.md | 286 ++++++++++-----------
.agents/skills/magpie-setup/agents.md | 198 ++++++++++++++
.../skills/magpie-setup/overrides.md | 0
.../skills/magpie-setup/unadopt.md | 119 +++++----
.../skills/magpie-setup/upgrade.md | 195 +++++++-------
{.github => .agents}/skills/magpie-setup/verify.md | 113 ++++----
.../skills/magpie-setup/worktree-init.md | 90 +++----
.../prepare-providers-documentation/SKILL.md | 0
.claude/skills/aip-user-stories | 2 +-
.claude/skills/airflow-translations | 1 +
.claude/skills/magpie-setup | 2 +-
.claude/skills/prepare-providers-documentation | 1 +
.github/skills/aip-user-stories | 1 +
.github/skills/airflow-translations | 1 +
.github/skills/magpie-setup | 1 +
.github/skills/magpie-setup/conventions.md | 278 --------------------
.github/skills/prepare-providers-documentation | 1 +
.gitignore | 6 +-
.pre-commit-config.yaml | 18 +-
dev/README_RELEASE_PROVIDERS.md | 4 +-
scripts/ci/prek/sync_translation_namespaces.py | 2 +-
44 files changed, 697 insertions(+), 705 deletions(-)
diff --git a/.github/skills/aip-user-stories/SKILL.md
b/.agents/skills/aip-user-stories/SKILL.md
similarity index 100%
rename from .github/skills/aip-user-stories/SKILL.md
rename to .agents/skills/aip-user-stories/SKILL.md
diff --git a/.github/skills/aip-user-stories/references/playbook-template.md
b/.agents/skills/aip-user-stories/references/playbook-template.md
similarity index 100%
rename from .github/skills/aip-user-stories/references/playbook-template.md
rename to .agents/skills/aip-user-stories/references/playbook-template.md
diff --git a/.github/skills/airflow-translations/SKILL.md
b/.agents/skills/airflow-translations/SKILL.md
similarity index 100%
rename from .github/skills/airflow-translations/SKILL.md
rename to .agents/skills/airflow-translations/SKILL.md
diff --git a/.github/skills/airflow-translations/locales/ar.md
b/.agents/skills/airflow-translations/locales/ar.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/ar.md
rename to .agents/skills/airflow-translations/locales/ar.md
diff --git a/.github/skills/airflow-translations/locales/ca.md
b/.agents/skills/airflow-translations/locales/ca.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/ca.md
rename to .agents/skills/airflow-translations/locales/ca.md
diff --git a/.github/skills/airflow-translations/locales/de.md
b/.agents/skills/airflow-translations/locales/de.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/de.md
rename to .agents/skills/airflow-translations/locales/de.md
diff --git a/.github/skills/airflow-translations/locales/el.md
b/.agents/skills/airflow-translations/locales/el.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/el.md
rename to .agents/skills/airflow-translations/locales/el.md
diff --git a/.github/skills/airflow-translations/locales/es.md
b/.agents/skills/airflow-translations/locales/es.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/es.md
rename to .agents/skills/airflow-translations/locales/es.md
diff --git a/.github/skills/airflow-translations/locales/fr.md
b/.agents/skills/airflow-translations/locales/fr.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/fr.md
rename to .agents/skills/airflow-translations/locales/fr.md
diff --git a/.github/skills/airflow-translations/locales/he.md
b/.agents/skills/airflow-translations/locales/he.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/he.md
rename to .agents/skills/airflow-translations/locales/he.md
diff --git a/.github/skills/airflow-translations/locales/hi.md
b/.agents/skills/airflow-translations/locales/hi.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/hi.md
rename to .agents/skills/airflow-translations/locales/hi.md
diff --git a/.github/skills/airflow-translations/locales/hu.md
b/.agents/skills/airflow-translations/locales/hu.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/hu.md
rename to .agents/skills/airflow-translations/locales/hu.md
diff --git a/.github/skills/airflow-translations/locales/it.md
b/.agents/skills/airflow-translations/locales/it.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/it.md
rename to .agents/skills/airflow-translations/locales/it.md
diff --git a/.github/skills/airflow-translations/locales/ja.md
b/.agents/skills/airflow-translations/locales/ja.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/ja.md
rename to .agents/skills/airflow-translations/locales/ja.md
diff --git a/.github/skills/airflow-translations/locales/ko.md
b/.agents/skills/airflow-translations/locales/ko.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/ko.md
rename to .agents/skills/airflow-translations/locales/ko.md
diff --git a/.github/skills/airflow-translations/locales/nl.md
b/.agents/skills/airflow-translations/locales/nl.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/nl.md
rename to .agents/skills/airflow-translations/locales/nl.md
diff --git a/.github/skills/airflow-translations/locales/pl.md
b/.agents/skills/airflow-translations/locales/pl.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/pl.md
rename to .agents/skills/airflow-translations/locales/pl.md
diff --git a/.github/skills/airflow-translations/locales/pt.md
b/.agents/skills/airflow-translations/locales/pt.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/pt.md
rename to .agents/skills/airflow-translations/locales/pt.md
diff --git a/.github/skills/airflow-translations/locales/th.md
b/.agents/skills/airflow-translations/locales/th.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/th.md
rename to .agents/skills/airflow-translations/locales/th.md
diff --git a/.github/skills/airflow-translations/locales/tr.md
b/.agents/skills/airflow-translations/locales/tr.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/tr.md
rename to .agents/skills/airflow-translations/locales/tr.md
diff --git a/.github/skills/airflow-translations/locales/zh-CN.md
b/.agents/skills/airflow-translations/locales/zh-CN.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/zh-CN.md
rename to .agents/skills/airflow-translations/locales/zh-CN.md
diff --git a/.github/skills/airflow-translations/locales/zh-TW.md
b/.agents/skills/airflow-translations/locales/zh-TW.md
similarity index 100%
rename from .github/skills/airflow-translations/locales/zh-TW.md
rename to .agents/skills/airflow-translations/locales/zh-TW.md
diff --git a/.github/skills/magpie-setup/SKILL.md
b/.agents/skills/magpie-setup/SKILL.md
similarity index 83%
rename from .github/skills/magpie-setup/SKILL.md
rename to .agents/skills/magpie-setup/SKILL.md
index d671d9cdced..15ccb89006d 100644
--- a/.github/skills/magpie-setup/SKILL.md
+++ b/.agents/skills/magpie-setup/SKILL.md
@@ -74,15 +74,22 @@ copy):
one records what each machine actually fetched. Drift
between them is surfaced and remediated by
`/magpie-setup upgrade`.
-- Symlinks from the adopter's skill directory into
- `<snapshot-dir>/skills/<framework-skill>/` make the
- framework's skills callable as if they lived in the adopter
- repo. **Each symlink is named `magpie-<framework-skill>`** —
- every framework skill is installed under a `magpie-` prefix
- so it is namespaced and never collides with the adopter's own
- skills (e.g. the snapshot's `skills/pr-management-triage/`
- becomes `.claude/skills/magpie-pr-management-triage`, invoked
- as `/magpie-pr-management-triage`). The symlinks are also
+- Symlinks make the framework's skills callable as if they
+ lived in the adopter repo. **Each symlink is named
+ `magpie-<framework-skill>`** — every framework skill is
+ installed under a `magpie-` prefix so it is namespaced and
+ never collides with the adopter's own skills (e.g. the
+ snapshot's `skills/pr-management-triage/` becomes
+ `magpie-pr-management-triage`, invoked as
+ `/magpie-pr-management-triage`). **`.agents/skills/` is the
+ one canonical home**: its `magpie-*` entries link into
+ `<snapshot-dir>/skills/<framework-skill>/`. Every other agent
+ target (`.claude/skills/`, `.github/skills/`, …) gets a thin
+ per-skill **relay** symlink that points back at the canonical
+ entry (`.claude/skills/magpie-<n>` →
+ `../../.agents/skills/magpie-<n>`) — no matter what layout the
+ adopting project previously used (see
+ [`agents.md`](agents.md)). The symlinks are
**gitignored** because their targets disappear on a fresh
clone before `/magpie-setup` runs.
- Adopter-specific modifications to framework workflows live as
@@ -98,11 +105,14 @@ copy):
repo that cannot be adopted via the snapshot mechanism is the
Apache Magpie framework checkout itself — a remote snapshot of the
framework into itself would be circular. Instead it **self-adopts**
-with `method:local`: each `magpie-<skill>` is a **committed**
-symlink into the in-repo `../../skills/<skill>/` source — written
-under both `.claude/skills/` (Claude Code) and `.github/skills/`
-(GitHub's skill loader) — with no snapshot, no remote fetch, and
-no copy. This makes
+with `method:local`: each canonical `magpie-<skill>` in
+`.agents/skills/` is a **committed** symlink into the in-repo
+`../../skills/<skill>/` source, and every other active agent
+target ([`agents.md`](agents.md)) — `.claude/skills/` (Claude
+Code), `.github/skills/` (GitHub's skill loader), and any present
+holdout — gets a committed **relay** symlink
+(`magpie-<skill>` → `../../.agents/skills/magpie-<skill>`) — with
+no snapshot, no remote fetch, and no copy. This makes
the framework's own skills callable while developing the framework,
and every contributor gets them active on a fresh clone with no
setup step. `adopt` detects the framework checkout structurally and
@@ -170,7 +180,7 @@ proposed `/magpie-setup upgrade`.
| [`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-magpie-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-magpie-overrides/` exists, drift status (committed vs local),
the `setup` skill itself is current. |
-| [`conventions.md`](conventions.md) | Adopter skills-dir convention
auto-detection — four patterns: A (flat `.claude/skills/<n>/`), B (per-skill
`.claude/skills/<n>` → `.github/skills/<n>/` double-symlink), C (none yet), D
(single directory symlink where one of `.claude/skills` / `.github/skills` is
itself a symlink to the other; two orientations). |
+| [`agents.md`](agents.md) | The agent-target registry — *which* directories
framework-skill symlinks land in across vendors, and the
**canonical-plus-relay** model: `.agents/skills/` is the one canonical home
(links into the snapshot/source); every other target (`claude-code`, `github`,
holdout natives like Windsurf / Goose) gets a per-skill relay symlink into
`.agents/skills/`. Defines active-target selection, SKILL.md format
portability, and the Claude-Code-only layer (sandbox/hooks). [...]
| [`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. |
| [`unadopt.md`](unadopt.md) | Reverse the adoption — remove snapshot, locks,
symlinks, post-checkout hook, `.gitignore` entries, the adoption sections in
`README.md` / `AGENTS.md` / `CONTRIBUTING.md`, and the committed `setup` skill
itself. Preserves `.apache-magpie-overrides/` by default; `--purge-overrides`
removes it too. Surfaces the full removal plan before any write. |
@@ -222,32 +232,44 @@ Three things gitignored in the adopter repo:
- `<snapshot-dir>` (the entire framework snapshot — gigabytes
potentially).
- `<local-lock>` (per-machine state).
-- The symlinks `setup adopt` creates in the adopter's
- skills directory (they target the gitignored snapshot, so
- they would dangle in a fresh clone).
-
-**Committed**: this skill (`setup`), the
+- The `magpie-*` symlinks `setup adopt` creates in every active
+ target dir — the canonical ones in `.agents/skills/` (they
+ target the gitignored snapshot) and the relays in
+ `.claude/skills/` / `.github/skills/` / holdouts (they target
+ the canonical entries) — both would dangle in a fresh clone.
+ The one exception un-ignored in each dir is `magpie-setup`.
+
+**Committed**: this skill (`setup`, as the canonical
+`.agents/skills/magpie-setup/` plus its relays), the
`<committed-lock>`, the `.apache-magpie-overrides/`
directory, the `.gitignore` entries themselves, any
project-doc updates the `adopt` sub-action makes.
-**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)). The `adopt` sub-action
-detects which pattern is in place and matches it.
+**Golden rule 5 — `.agents/skills/` is canonical; everything
+else just relays into it.** Regardless of how an adopting
+project previously organised its `.claude/skills/` or
+`.github/skills/`, `adopt` always wires the framework the same
+way: the canonical `magpie-*` links live in `.agents/skills/`,
+and every other active target (`.claude`, `.github`, holdouts)
+gets per-skill relay symlinks pointing back at the canonical
+entries (`.claude/skills/magpie-<n>` →
+`../../.agents/skills/magpie-<n>`). The adopter's own native
+(non-`magpie-`) skills in those dirs are left untouched. See
+[`agents.md`](agents.md).
**Golden rule 6 — copy this skill, symlink the rest; all under
the `magpie-` prefix.** This skill (source `skills/setup/`) is
the **only** framework skill that gets **copied** into an
-adopter repo — committed as `.claude/skills/magpie-setup/`.
-All other framework skills are **symlinked** into the
-gitignored snapshot, each named `magpie-<framework-skill>`
+adopter repo — committed as the canonical
+`.agents/skills/magpie-setup/`, with committed relay symlinks to
+it from `.claude/skills/magpie-setup` and
+`.github/skills/magpie-setup`. All other framework skills are
+**symlinked** (canonical link into the gitignored snapshot, plus
+relays), each named `magpie-<framework-skill>`
(e.g. `magpie-security-issue-import` →
`<snapshot-dir>/skills/security-issue-import/`).
The `magpie-` prefix namespaces every framework skill so it
never collides with an adopter's own skills. Mixing copy and
symlink — copying a security skill, for instance — creates a
-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>`)
@@ -301,7 +323,7 @@ modified files of this skill before continuing** the rest of
the current run. Concretely: after the copy lands on disk,
re-load `SKILL.md` and the sub-action file you are
currently executing (and any helper file you have already
-opened, such as `conventions.md` or `overrides.md`), then
+opened, such as `agents.md` or `overrides.md`), then
resume from the step after the overwrite. The reload runs as
the **first thing** that happens after the overwrite, before
any further reconciliation, symlink work, or doc updates.
@@ -358,6 +380,7 @@ first, then continue.
|---|---|
| `from:<git-ref>` / `from:<version>` | Adopt or upgrade from a specific
framework ref or version. Used during `adopt` (overrides the user prompt) and
`upgrade` (overrides the committed lock for *this run only* — does NOT update
the committed lock). |
| `method:<git-branch\|git-tag\|svn-zip\|local>` | Pick the install method
explicitly. Default during `adopt`: prompt the user. **`local`** is
**framework-checkout only** — it self-adopts by linking the in-repo `skills/`
source directly instead of fetching a snapshot (see [`adopt.md` → Local
self-adoption](adopt.md#local-self-adoption-methodlocal)). |
+| `agents:<list>` | Comma-separated **agent targets** to wire symlinks into
([`agents.md`](agents.md) registry ids: `universal`, `claude-code`, `github`,
`windsurf`, `goose`, …). Default on `adopt`/`upgrade`: auto — the always-on
neutral set (`universal` + `claude-code` + `github`) plus any other registry
dir already present in the repo. When passed, **replaces** the auto-detected
set for that run, except `universal` (`.agents/skills/`) which is always
retained because it is the canonica [...]
| `skill-families:<list>` | Comma-separated **opt-in** families to symlink
(`security`, `pr-management`, `issue`). Default on `adopt`: prompt. Default on
`upgrade`: read the families list from `<committed-lock>` / `<local-lock>`,
**auto-include any opt-in family the framework has introduced since the lock
was written** (recorded back into the lock), and **ensure every framework skill
in the effective family set has a valid symlink** — create or repair missing /
broken symlinks, not just [...]
| `--purge-overrides` | *(unadopt only)* Also `git rm -r`
`.apache-magpie-overrides/`. Default: preserve. |
| `dry-run` | Show what the skill would do without writing anything. |
diff --git a/.github/skills/magpie-setup/adopt.md
b/.agents/skills/magpie-setup/adopt.md
similarity index 86%
rename from .github/skills/magpie-setup/adopt.md
rename to .agents/skills/magpie-setup/adopt.md
index b7b508b2316..ea7e8af5559 100644
--- a/.github/skills/magpie-setup/adopt.md
+++ b/.agents/skills/magpie-setup/adopt.md
@@ -88,44 +88,22 @@ between automatically:
is refused.
- **Adopter repo** (the structural markers are absent) →
continue with the normal remote-snapshot flow below.
-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.
-
- If detection returns *"ambiguous → propose Pattern D
- consolidation"* (both `.claude/skills/` and
- `.github/skills/` exist as regular directories with
- independent, non-aliased content), run the
- **Pre-Pattern-D consolidation** flow described under
- [section D of
`conventions.md`](conventions.md#d-single-directory-symlink--one-of-claudeskills--githubskills-is-a-symlink-to-the-other)
- before continuing:
-
- - List the skills in each directory with their content
- fingerprint (real dir vs symlink, target if symlink,
- SKILL.md presence).
- - Flag any name collisions where the two sides have
- different content for the same name.
- - Use a structured prompt (`AskUserQuestion` when the
- harness offers one) with three options: **D.1**
- (consolidate under `.github/skills/`), **D.2**
- (consolidate under `.claude/skills/`), or **decline**
- (fall back to Pattern A treating `.claude/skills/` as
- canonical and leaving `.github/skills/` alone).
- - On D.1 / D.2 confirmation: move every skill from the
- side that will become the symlink into the side that
- will become the real directory (resolving any flagged
- name collisions first — never auto-rename adopter
- content), then replace the now-empty side with a
- relative symlink to the other side, then re-run
- detection to confirm the pattern is now D.
- - If the user declines or unresolved name collisions
- block consolidation, fall back to Pattern A and pin
- `<adopter-skills-dir>` = `.claude/skills/` as usual.
-
- The consolidation is a one-time, deliberate layout
- change; the adopt flow surfaces every step before
- writing.
+4. Compute the **active target set** per
+ [`agents.md`](agents.md): the always-on neutral targets
+ (`universal` = `.agents/skills/`, the canonical home; plus
+ the `claude-code` + `github` relay pair), any other registry
+ dir already present in the repo, and any `agents:<list>`
+ opt-in. **`.agents/skills/` is always canonical** — its
+ `magpie-*` entries are the links into the snapshot; every
+ other active target gets per-skill relay symlinks into it.
+
+ There is **no skills-dir convention to detect**: regardless
+ of how the adopter previously organised `.claude/skills/` or
+ `.github/skills/`, the framework always wires the `magpie-*`
+ set the same way (canonical in `.agents/skills/`, relayed
+ elsewhere) and leaves the adopter's own native (non-`magpie-`)
+ skills in place. Pin `.agents/skills/` as the canonical dir
+ for the rest of this flow.
## Local self-adoption (`method:local`)
@@ -149,12 +127,14 @@ How it differs from a remote adoption:
- **Symlinks are committed, not gitignored.** Each
`magpie-<skill>` symlink targets `../../skills/<skill>/` — an
in-repo path that always resolves on a fresh clone — so the
- links are committed. They are written under **both**
- `.claude/skills/` (Claude Code) and `.github/skills/` (GitHub's
- skill loader), so the framework's own skills are discoverable
- by either harness; `.gitignore` un-ignores `magpie-*` in both
- dirs. Every contributor gets the skills active with no setup
- step.
+ links are committed. They are written under **every active
+ target dir** ([`agents.md`](agents.md)) — `.agents/skills/`
+ (the universal path shared by Codex, Cursor, Gemini CLI,
+ Copilot, …), `.claude/skills/` (Claude Code), and
+ `.github/skills/` (GitHub's skill loader) — so the framework's
+ own skills are discoverable by any harness; `.gitignore`
+ un-ignores `magpie-*` in each. Every contributor gets the
+ skills active with no setup step, whatever agent they use.
- **All skills, no family prompt.** Self-adoption links *every*
skill under `skills/`, so the opt-in family prompt of
[Step 5](#step-5--pick-the-skill-families) is skipped.
@@ -192,27 +172,44 @@ How it differs from a remote adoption:
```
5. **`.gitignore`.** Ensure each skills dir glob is ignored with
- the `magpie-*` set un-ignored, in **both** locations
- (idempotent — add any missing line):
+ the `magpie-*` set un-ignored, in **every active target
+ location** ([`agents.md`](agents.md) — the always-on neutral
+ targets `.agents/skills/`, `.claude/skills/`, `.github/skills/`
+ plus any other registry dir already present). Idempotent — add
+ any missing line. For the default set:
```text
+ .agents/skills/*
+ !/.agents/skills/magpie-*
.claude/skills/*
!/.claude/skills/magpie-*
.github/skills/*
!/.github/skills/magpie-*
```
-6. **Create the symlinks.** For each enumerated skill `<n>`,
- create the relative symlink `magpie-<n>` → `../../skills/<n>`
- under **both** `<repo-root>/.claude/skills/` and
- `<repo-root>/.github/skills/`. Idempotent: re-point a
- pre-existing `magpie-<n>` symlink only if it targets something
- else; never overwrite a non-symlink (surface the conflict and
- stop). Show the full list and confirm before writing.
-7. **Verify + stage.** Confirm every `magpie-<n>` symlink (in
- both dirs) resolves to a directory containing `SKILL.md`, then
- suggest the user `git add` the symlinks, `.apache-magpie.lock`,
- and `.gitignore`.
+ (Self-adoption symlinks are *committed*, not gitignored — see
+ the next step — so the `!…/magpie-*` negation here un-ignores
+ the whole set, not just `magpie-setup`.)
+
+6. **Create the symlinks** (canonical first, then relays). For
+ each enumerated skill `<n>`:
+ - **Canonical** — create `.agents/skills/magpie-<n>` →
+ `../../skills/<n>` (the in-repo source).
+ - **Relays** — for every *other* active target dir from
+ [`agents.md`](agents.md) (`.claude/skills/`, `.github/skills/`,
+ plus any present holdout), create `magpie-<n>` →
+ `../../.agents/skills/magpie-<n>` (pointing back at the
+ canonical entry).
+
+ Idempotent: re-point a pre-existing `magpie-<n>` symlink only
+ if it targets something else; never overwrite a non-symlink
+ (surface the conflict and stop). Show the full list and
+ confirm before writing.
+7. **Verify + stage.** Confirm every canonical `magpie-<n>`
+ symlink resolves to a directory containing `SKILL.md`, and
+ every relay resolves through `.agents/skills/magpie-<n>` to the
+ same, then suggest the user `git add` the symlinks,
+ `.apache-magpie.lock`, and `.gitignore`.
Self-adoption skips the adopter-only steps entirely: no snapshot
fetch (Step 3), no committed-`setup` reconcile (Step 3b), no
@@ -299,24 +296,24 @@ snapshot's version before the rest of this run executes —
otherwise we finish adoption against the *old* bootstrap
logic for a *new* framework version.
-1. Diff `<adopter-skills-dir>/magpie-setup/` against
+1. Diff the canonical committed copy
+ `.agents/skills/magpie-setup/` against
`.apache-magpie/skills/setup/`.
2. If they match — skip the rest of this step.
3. If they differ and the adopter has **no** local
modifications beyond what the snapshot ships — overwrite
- the committed copy from the snapshot:
+ the canonical committed copy from the snapshot:
```bash
- # Flat layout:
- rm -rf <adopter-skills-dir>/magpie-setup
+ rm -rf .agents/skills/magpie-setup
cp -r .apache-magpie/skills/setup \
- <adopter-skills-dir>/magpie-setup
-
- # Double-symlinked layout: copy into .github/skills/ —
- # the .claude/skills/magpie-setup symlink already
- # points at it.
+ .agents/skills/magpie-setup
```
+ The relay symlinks (`.claude/skills/magpie-setup`,
+ `.github/skills/magpie-setup`) point at
+ `../../.agents/skills/magpie-setup` and need no change.
+
4. If the adopter **does** have local modifications,
surface the diff and stop. The user either (a) confirms
the local mods can be discarded, (b) upstreams them as a
@@ -325,10 +322,10 @@ logic for a *new* framework version.
continues against the in-flight (older) version with a
warning.
5. **Reload in-flight.** Immediately after the copy lands,
- re-read `<adopter-skills-dir>/magpie-setup/SKILL.md`
- and `<adopter-skills-dir>/magpie-setup/adopt.md` (the
+ re-read `.agents/skills/magpie-setup/SKILL.md`
+ and `.agents/skills/magpie-setup/adopt.md` (the
current sub-action file), plus any helper file already
- open in this run (`conventions.md`, `overrides.md`),
+ open in this run (`agents.md`, `overrides.md`),
before continuing to Step 4. The remaining steps run
against the just-loaded content.
@@ -508,51 +505,30 @@ idempotent — re-add them if they're missing.
/.claude/settings.local.json
```
-**Symlink-pattern entries — vary by adopter
-[skills-dir convention](conventions.md)**:
-
- Every framework skill is symlinked under the `magpie-`
- prefix (see [`SKILL.md` Golden rule 6](SKILL.md#golden-rules)),
- so a single `magpie-*` glob covers them all — no per-family
- lines.
-
-- **Pattern A (flat)** — only the `.claude/skills/...` lines:
-
- ```text
- /.claude/skills/magpie-*
- !/.claude/skills/magpie-setup
- ```
-
-- **Pattern B (double-symlinked)** — both `.claude/skills/...`
- AND `.github/skills/...` lines, because each framework skill
- has two physical symlinks (outer at `.claude/skills/magpie-<n>`,
- inner at `.github/skills/magpie-<n>`):
+**Symlink entries — one uniform block per active target
+([`agents.md`](agents.md)), no per-layout variation.** Every
+framework skill is symlinked under the `magpie-` prefix (see
+[`SKILL.md` Golden rule 6](SKILL.md#golden-rules)), so a single
+`magpie-*` glob covers them all in each target dir — no per-family
+lines. The canonical target (`.agents/skills/`) and every relay
+target (`.claude/skills/`, `.github/skills/`, any present holdout)
+get the **same** two-line block, keyed on the target's own dir:
- ```text
- /.claude/skills/magpie-*
- !/.claude/skills/magpie-setup
- /.github/skills/magpie-*
- !/.github/skills/magpie-setup
- ```
-
-- **Pattern D (single directory symlink)** — only the
- *canonical-side* `.../skills/...` line. With D.1
- (canonical = `.github/skills/`):
-
- ```text
- /.github/skills/magpie-*
- !/.github/skills/magpie-setup
- ```
-
- With D.2 (canonical = `.claude/skills/`), use
- `/.claude/skills/magpie-*` (plus `!/.claude/skills/magpie-setup`)
- instead. Pattern D does not
- need ignore lines on the *symlinked* side because that side
- is itself a single tracked symlink — git does not descend
- into it, so the symlinked-side paths match no tracked file.
+```text
+/.agents/skills/magpie-*
+!/.agents/skills/magpie-setup
+/.claude/skills/magpie-*
+!/.claude/skills/magpie-setup
+/.github/skills/magpie-*
+!/.github/skills/magpie-setup
+```
-- **Pattern C (none yet)** — same as the pattern the user
- picks during adopt (defaults to A).
+Add the analogous two lines for any present holdout
+(`.windsurf/skills/`, `.goose/skills/`, …). The relay symlinks are
+gitignored exactly like the canonical ones: a relay points at
+`../../.agents/skills/magpie-<n>`, which itself targets the
+gitignored snapshot, so it dangles on a fresh clone before
+`/magpie-setup` runs.
The `magpie-*` glob covers both the opt-in families and the
always-on families (`magpie-setup-*` and the `magpie-list-*`
@@ -577,11 +553,11 @@ adopt flow checks for the line and adds it if missing.
## Step 8 — Wire up the framework-skill symlinks
-The skill walks `<snapshot-dir>/skills/` and creates
-a gitignored symlink for every framework skill the adopter
-should have callable, at `<adopter-skills-dir>/magpie-<skill>` →
-relative path into
-`<snapshot-dir>/skills/<skill>/`.
+The skill walks `<snapshot-dir>/skills/` and, for every
+framework skill the adopter should have callable, creates a
+gitignored **canonical** symlink at `.agents/skills/magpie-<skill>`
+→ relative path into `<snapshot-dir>/skills/<skill>/`, plus a
+**relay** symlink in every other active target dir.
The set of skills to link is the **union** of:
@@ -601,28 +577,28 @@ adoption path where the committed lock only records the
opt-in pick. Compute the family glob fresh from the snapshot
contents on disk — do not hard-code skill names.
-Per-pattern symlink wiring (see
-[`conventions.md`](conventions.md)):
+Symlink wiring (targets from [`agents.md`](agents.md)) — the
+**canonical-plus-relay** model, applied identically no matter
+what layout the adopter's `.claude/` / `.github/` were in before:
Every symlink is named `magpie-<n>` (the `magpie-` prefix
-namespaces framework skills); its target keeps the snapshot's
-clean source name `skills/<n>/`.
-
-- **Pattern A (flat)** — one symlink per skill at
- `.claude/skills/magpie-<n>` → snapshot. Gitignored.
-- **Pattern B (double-symlinked)** — two symlinks per skill:
- the inner one in `.github/skills/magpie-<n>` → snapshot, the
- outer `.claude/skills/magpie-<n>` →
- `../../.github/skills/magpie-<n>/`. Both gitignored.
-- **Pattern D (single directory symlink)** — one symlink per
- skill at the *canonical-side* `<canonical>/skills/magpie-<n>`
- → snapshot. **Skip the symlinked side entirely** — one of
- `.claude/skills` / `.github/skills` is itself a directory
- symlink into the other, so the symlinked-side path is
- automatically resolved. With D.1 the canonical side is
- `.github/skills/`; with D.2 it is `.claude/skills/`.
- Gitignored.
-- **Pattern C (none yet)** — same as A.
+namespaces framework skills). Wire the **same set of skills**
+into **every active target dir**, canonical first:
+
+- **Canonical target (`.agents/skills/`)** — one symlink per
+ skill at `.agents/skills/magpie-<n>` → relative path into the
+ snapshot (`../../.apache-magpie/skills/<n>/`). Gitignored. This
+ is the single placement that makes the framework discoverable to
+ Codex, Cursor, Gemini CLI, Copilot, OpenCode, and the rest of
+ the shared-path cluster, and the one source every relay points
+ at.
+
+- **Relay targets (`.claude/skills/`, `.github/skills/`, any
+ present holdout)** — one symlink per skill at
+ `<target>/skills/magpie-<n>` → `../../.agents/skills/magpie-<n>`
+ (pointing back at the canonical entry, **not** the snapshot).
+ Gitignored. The adopter's own native (non-`magpie-`) skills in
+ these dirs are left untouched.
**Never overwrite an existing committed skill** of the same
name. Surface conflicts and stop. The bootstrap `setup` skill
@@ -934,7 +910,7 @@ the user before writing:
# apache-steward post-checkout hook (installed by /magpie-setup adopt).
# Add the current worktree's working dir to the worktree's own
# .claude/settings.local.json sandbox allowlists (per issue #197).
-# Chains into the helper if installed by /setup-isolated-setup-install;
+# Chains into the helper if installed by /magpie-setup-isolated-setup-install;
# no-op when the helper is absent.
set -u
if [ -x "$HOME/.claude/scripts/sandbox-add-project-root.sh" ]; then
@@ -947,7 +923,7 @@ The `|| true` guard keeps the hook from failing the
surrounding
git operation (`git checkout`, `git worktree add`) — the hook is
best-effort reconciliation, not a gate.
-If the operator has not yet run `/setup-isolated-setup-install`,
+If the operator has not yet run `/magpie-setup-isolated-setup-install`,
the helper-script line is a no-op (the `-x` test fails). When
they later install the secure setup, no hook re-write is needed:
the next `post-checkout` fires the helper automatically.
@@ -1004,7 +980,7 @@ framework before they hit a "skill not found" error:
[`.apache-magpie.lock`](.apache-magpie.lock). The only
framework artefact committed to this repo is the
`setup` skill at
- [`.github/skills/magpie-setup/`](.github/skills/magpie-setup/);
+ [`.agents/skills/magpie-setup/`](.agents/skills/magpie-setup/);
everything else is a gitignored symlink the setup skill
wires up.
@@ -1013,7 +989,7 @@ framework before they hit a "skill not found" error:
/magpie-setup
- (or follow [`.claude/skills/magpie-setup/`](.claude/skills/magpie-setup/))
+ (or follow [`.agents/skills/magpie-setup/`](.agents/skills/magpie-setup/))
to fetch the snapshot per the committed lock, scaffold the
gitignored symlinks, and install the post-checkout hook
that re-creates them on each worktree checkout.
@@ -1027,10 +1003,8 @@ framework before they hit a "skill not found" error:
Trim the skill-family list to what was actually picked in
Step 5 (only mention `security-*` if the adopter installed
- that family, etc.). Adjust the skill paths to the adopter's
- convention (flat / double-symlinked / single-directory-symlink
- — see [`conventions.md`](conventions.md)). Skip this sub-step
- entirely if `README.md` does not exist.
+ that family, etc.). Skip this sub-step entirely if
+ `README.md` does not exist.
2. **`AGENTS.md` (agent-facing detail, ONLY if the file
already exists).** Agent harnesses load this file
@@ -1053,7 +1027,7 @@ framework before they hit a "skill not found" error:
A fresh clone needs the snapshot populated before any
framework skill is invocable. Run `/magpie-setup` (or
- follow [`.claude/skills/magpie-setup/`](.claude/skills/magpie-setup/))
+ follow [`.agents/skills/magpie-setup/`](.agents/skills/magpie-setup/))
to fetch it per the committed
[`.apache-magpie.lock`](.apache-magpie.lock). The
contributor-facing summary of the adoption + setup flow
@@ -1110,7 +1084,7 @@ Four passes, in this order:
2. **Propagate to every worktree (run `worktree-init`
unconditionally).** The main is now adopted; any
pre-existing linked worktree of this repo still lacks
- the snapshot symlink and the `<adopter-skills-dir>`
+ the snapshot symlink and the per-target framework-skill
symlinks. `worktree-init` is **always run on every
worktree** at the end of adopt, even when none exist
yet, even when the worktree appears wired, because
@@ -1133,8 +1107,8 @@ Four passes, in this order:
the family set from `<main>/.apache-magpie.lock` plus
the always-on families per
[`SKILL.md` Golden rule 8](SKILL.md#golden-rules), and
- reconciles both the snapshot symlink and the
- `<adopter-skills-dir>` symlinks (see
+ reconciles both the snapshot symlink and the canonical +
+ relay framework-skill symlinks (see
[`worktree-init.md` Step 1 + Step 1b](worktree-init.md)).
- Collect each invocation's recap into a per-worktree
row in the adopt summary's `Worktrees:` section.
@@ -1185,7 +1159,7 @@ Four passes, in this order:
- **Helper absent** (`~/.claude/scripts/sandbox-add-project-root.sh`
does not exist) → surface as ⚠ in the adopt summary with a
- pointer at `/setup-isolated-setup-install`. Do not block
+ pointer at `/magpie-setup-isolated-setup-install`. Do not block
adopt — many adopters set up secure-agent isolation later,
and the framework-skill symlinks are usable without it (the
adopter just runs Bash outside the sandbox until they wire
@@ -1231,16 +1205,18 @@ Committed (you'll see in `git status`):
.gitignore
.apache-magpie.lock
.apache-magpie-overrides/README.md
- <adopter-skills-dir>/magpie-setup/ (this skill itself)
+ .agents/skills/magpie-setup/ (this skill itself — canonical copy)
+ .claude/skills/magpie-setup (relay symlink →
../../.agents/skills/magpie-setup)
+ .github/skills/magpie-setup (relay symlink →
../../.agents/skills/magpie-setup)
README.md (or CONTRIBUTING.md)
Gitignored (do NOT commit):
.apache-magpie/
.apache-magpie.local.lock
- <adopter-skills-dir>/magpie-* (except magpie-setup, committed above) #
every framework skill: opt-in + always-on families
- # Pattern A: <adopter-skills-dir> = .claude/skills/
- # Pattern B: <adopter-skills-dir> = both .claude/skills/ AND .github/skills/
- # Pattern D: <adopter-skills-dir> = .github/skills/ only
+ .agents/skills/magpie-* (except magpie-setup, committed above) #
canonical links into the snapshot: opt-in + always-on families
+ .claude/skills/magpie-* (except magpie-setup, committed above) # relays →
../../.agents/skills/magpie-*
+ .github/skills/magpie-* (except magpie-setup, committed above) # relays →
../../.agents/skills/magpie-*
+ # plus the same two lines for any present holdout (.windsurf/skills/,
.goose/skills/, …)
```
Then suggest the user `git add` the committed files and open
diff --git a/.agents/skills/magpie-setup/agents.md
b/.agents/skills/magpie-setup/agents.md
new file mode 100644
index 00000000000..f8210e6d6b7
--- /dev/null
+++ b/.agents/skills/magpie-setup/agents.md
@@ -0,0 +1,198 @@
+<!-- SPDX-License-Identifier: Apache-2.0
+ https://www.apache.org/legal/release-policy.html -->
+
+# agents — the agent-target registry (where framework-skill symlinks land)
+
+Framework skills are **vendor-neutral content**: every supported
+agent reads the *same* `SKILL.md` (the open Agent Skills format —
+plain Markdown + a small YAML frontmatter). The skill body is
+byte-identical no matter which agent loads it; there is **no
+per-agent compile, adapter, or content transform**. The only thing
+that genuinely differs between agents is **where on disk each one
+looks for skills**. This file is the registry of those locations —
+the single source of truth `adopt`, `upgrade`, `verify`,
+`unadopt`, and `worktree-init` consult to decide *which directories*
+to wire, refresh, health-check, and tear down.
+
+It is the magpie analogue of a package manager's per-agent path
+table: keep all vendor-specific knowledge here as *"where files
+go"*, never as *"what files contain"*.
+
+## The registry
+
+| Target id | Project skills dir | Kind | Reads it |
+|---|---|---|---|
+| `universal` | `.agents/skills/` | universal **(canonical)** | Codex, Cursor,
Gemini CLI, GitHub Copilot, OpenCode, Cline, Zed, Warp, Amp, and the rest of
the cluster that converged on the shared path |
+| `claude-code` | `.claude/skills/` | native (relay) | Claude Code |
+| `github` | `.github/skills/` | native (relay) | GitHub's skill loader |
+| `windsurf` | `.windsurf/skills/` | native (relay) | Windsurf |
+| `goose` | `.goose/skills/` | native (relay) | Goose |
+
+The table is **extensible**: a new agent that wants framework
+skills is one new row (`id`, project dir, kind), nothing else —
+the same way a path-registry-driven installer adds an agent. Do
+not invent per-agent *content*; if an agent needs a different
+directory, add a row, never a forked skill.
+
+## The canonical directory — `.agents/skills/`
+
+`.agents/skills/` is the **one canonical home** for every framework
+skill. Its `magpie-<skill>` entries are the links that resolve to
+the actual skill source — the gitignored snapshot
+(`.apache-magpie/skills/<skill>/`) for a normal adopter, or the
+in-repo `../../skills/<skill>/` source for the framework's own
+[local self-adoption](adopt.md#local-self-adoption-methodlocal).
+
+This is the load-bearing move for neutrality on two fronts:
+
+1. **One placement covers the whole shared-path cluster.** A large
+ set of agents (Codex, Cursor, Gemini CLI, GitHub Copilot,
+ OpenCode, Cline, Zed, Warp, …) all read `.agents/skills/` as
+ their project-scope skills path, so a single
+ `.agents/skills/magpie-<skill>` link is seen by all of them:
+
+ ```text
+ .agents/skills/magpie-pr-management-triage → the canonical link
+ ├─ Codex picks it up
+ ├─ Cursor picks it up
+ ├─ Gemini CLI picks it up
+ └─ Copilot … picks it up
+ ```
+
+2. **Every other target is a thin relay into it.** Agents with a
+ bespoke folder (`claude-code` → `.claude/skills/`, `github` →
+ `.github/skills/`, `windsurf`, `goose`, …) do **not** link into
+ the snapshot independently. Each one gets a per-skill relay
+ symlink that points back at the canonical entry:
+
+ ```text
+ .claude/skills/magpie-<skill> → ../../.agents/skills/magpie-<skill>
+ .github/skills/magpie-<skill> → ../../.agents/skills/magpie-<skill>
+ ```
+
+ The snapshot path appears exactly **once** — in
+ `.agents/skills/`. Re-pointing the framework at a new snapshot,
+ or repairing a broken link, is a single-source operation; the
+ relays follow automatically. Adopters keep their own native
+ (non-`magpie-`) skills in `.claude/skills/` / `.github/skills/`
+ untouched — only the `magpie-*` entries are relayed.
+
+(Global / per-user skill paths diverge across agents — e.g.
+`~/.cursor/skills/`, `~/.codex/skills/`, `~/.gemini/skills/`. The
+framework's adoption is **project-scope** — it writes inside the
+adopter repo — so it only ever cares about the project columns
+above. Global installs are the operator's concern, out of scope
+for `setup`.)
+
+## Active-target selection — which dirs `adopt` wires
+
+On every `adopt` / `upgrade` / `worktree-init`, the **active
+target set** is computed as the union of:
+
+1. **The always-on neutral targets** — `universal`
+ (`.agents/skills/`, canonical) **plus** the `claude-code` +
+ `github` relay pair. These three are wired unconditionally; the
+ relays are cheap relative symlinks, harmless to an agent that
+ never reads them, and dropping them is not a supported
+ configuration.
+2. **Any other registry target already present in the repo** —
+ if `.windsurf/skills/` or `.goose/skills/` (etc.) already
+ exists as a real directory, it is added to the active set so
+ that agent sees the framework skills too (as a relay).
+3. **Explicit opt-in** via the `agents:<list>` flag (see
+ [`SKILL.md` Inputs](SKILL.md#inputs)) — a comma-separated list
+ of registry ids. When passed it **replaces** the auto-detected
+ set (1)+(2) for that run; `universal` is always retained even
+ if omitted, because it is the canonical home every relay points
+ at — dropping it would leave the relays dangling.
+
+The flow **never** removes or rewrites an adopter's own
+non-`magpie-` skill content in any target dir. It only adds /
+repairs `magpie-*` symlinks. Whatever layout an adopter's
+`.claude/` / `.github/` directories were in before, the framework
+always wires the `magpie-*` set the same way: canonical in
+`.agents/skills/`, relayed everywhere else.
+
+## How the framework's rules generalise across targets
+
+Every adoption rule is "canonical link, then relays", not
+"per-target independent link":
+
+- **`magpie-` prefix** ([`SKILL.md` Golden rule 6](SKILL.md#golden-rules))
+ — unchanged. Every framework skill is `magpie-<skill>` in
+ *every* active target dir, so it never collides with an
+ adopter's own skills regardless of agent.
+- **`.gitignore`** — one **uniform** block per active target dir,
+ with no per-layout variation: `/<dir>/magpie-*` ignored plus
+ `!/<dir>/magpie-setup` un-ignored. The negation keeps the one
+ committed bootstrap (`magpie-setup`) tracked; the glob ignores
+ the rest (the canonical links target the gitignored snapshot, so
+ the relays that follow them dangle on a fresh clone). See
+ [`adopt.md` Step 7](adopt.md#step-7--gitignore-entries-fresh-only).
+- **Symlink wiring** — the canonical `magpie-<n>` →
+ snapshot/source link is created once in `.agents/skills/`; every
+ other active target (`claude-code`, `github`, `windsurf`,
+ `goose`, …) gets a per-skill relay `magpie-<n>` →
+ `../../.agents/skills/magpie-<n>`. See
+ [`adopt.md` Step 8](adopt.md#step-8--wire-up-the-framework-skill-symlinks).
+- **Committed bootstrap** ([`SKILL.md` Golden rule 6](SKILL.md#golden-rules))
+ — the one committed framework artefact, `magpie-setup`, lives at
+ the **canonical** `.agents/skills/magpie-setup/` (a committed
+ copy for adopters; a committed symlink under self-adoption).
+ `.claude`/`.github` carry a committed relay symlink to it.
+- **Local self-adoption** (framework checkout) — canonical
+ committed symlinks into `../../skills/<skill>/` in
+ `.agents/skills/`, plus committed relays into
+ `../../.agents/skills/magpie-<skill>` in every other active
+ target. See
+ [`adopt.md` → Local self-adoption](adopt.md#local-self-adoption-methodlocal).
+- **`unadopt` / `worktree-init`** — every active target dir is
+ torn down / propagated uniformly. Removing only `.claude` +
+ `.github` would orphan the canonical `.agents/skills/magpie-*`
+ links; removing only `.agents` would leave every relay dangling.
+
+## SKILL.md format portability
+
+The same `SKILL.md` is valid in every target with no
+per-agent edit:
+
+| Frontmatter field | Cross-agent behaviour |
+|---|---|
+| `name`, `description` | Universal — discovery works everywhere. |
+| `when_to_use` | Claude-family routing hint; other agents may ignore it →
discovery still works off `description`, only routing precision degrades. |
+| `argument-hint`, `capability` | magpie / Claude extensions; non-supporting
agents silently ignore them. |
+| `license` | Inert metadata. |
+
+Unknown frontmatter is ignored by each agent (graceful
+degradation), so there is **no compile step and no per-agent
+file**. The gitignored snapshot stays the single source of truth;
+`.agents/skills/` links into it, and every other target dir
+resolves into it through the `.agents/skills/` relay.
+
+## The Claude-Code-only layer (not wired for other targets)
+
+Some of what `adopt` installs is **genuinely Claude-Code-specific
+and is wired only when the `claude-code` target is active**:
+
+- `.claude/settings.json` — the sandbox (`network.allowedDomains`
+ allowlist, `filesystem.denyRead`), the MCP-tool permission
+ allowlist, and the hooks. Schema:
+ `claude-code-settings.json`.
+- `.claude/settings.local.json` — per-machine sandbox-allowlist
+ entries.
+- The `setup-isolated-setup-*` skill family — sandbox / pinned-
+ tools / hooks installer.
+
+Other agents adopt the **skills** (the neutral content) **without**
+this layer.
+
+> **Security caveat — this layer is a control, not cosmetics.**
+> For a security framework the sandbox is a *confidentiality
+> control* (it blocks exfiltration of non-public vulnerability
+> data and reading `~/`). Running a security-class skill on an
+> agent that lacks an equivalent control is a **policy decision**,
+> not graceful degradation. Adopting the skills onto a non-Claude
+> agent is supported; *executing confidential workflows there*
+> requires the project to either declare that agent unsupported
+> for those workflows or provide an equivalent control. `adopt`
+> itself only places files — it does not grant that approval.
diff --git a/.github/skills/magpie-setup/overrides.md
b/.agents/skills/magpie-setup/overrides.md
similarity index 100%
rename from .github/skills/magpie-setup/overrides.md
rename to .agents/skills/magpie-setup/overrides.md
diff --git a/.github/skills/magpie-setup/unadopt.md
b/.agents/skills/magpie-setup/unadopt.md
similarity index 72%
rename from .github/skills/magpie-setup/unadopt.md
rename to .agents/skills/magpie-setup/unadopt.md
index ba9ef9255a7..a09f33efa89 100644
--- a/.github/skills/magpie-setup/unadopt.md
+++ b/.agents/skills/magpie-setup/unadopt.md
@@ -6,10 +6,19 @@
The reverse of [`adopt.md`](adopt.md). Removes the framework
artefacts the adopt flow installed — gitignored snapshot,
committed lock, gitignored local lock, framework-skill
-symlinks, `.gitignore` entries, post-checkout hook, the
-adoption sections in `README.md` / `AGENTS.md` /
-`CONTRIBUTING.md`, and the committed `setup` skill
-itself.
+symlinks **in every active target dir** ([`agents.md`](agents.md)
+— `.agents/skills/`, `.claude/skills/`, `.github/skills/`, plus
+any present holdout), the matching `.gitignore` blocks,
+post-checkout hook, the adoption sections in `README.md` /
+`AGENTS.md` / `CONTRIBUTING.md`, and the committed `setup`
+skill itself.
+
+> **Critical — tear down *all* target dirs.** Removing only the
+> `.claude/skills/` + `.github/skills/` pair would **orphan** the
+> `.agents/skills/magpie-*` links (and any holdout's) — dangling
+> symlinks into a snapshot that no longer exists, plus stale
+> `.gitignore` blocks. The removal must cover every active target
+> dir per [`agents.md`](agents.md).
By default the adopter-authored `.apache-magpie-overrides/`
directory is **preserved** — it contains hand-written
@@ -70,9 +79,12 @@ relevant override file rather than unadopting.
lock, the adopter ran the install recipe but never
completed `/magpie-setup adopt`; treat that as not-yet-
adopted and stop with the same message.)
-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.
+5. Compute the **active target set** per
+ [`agents.md`](agents.md): the canonical `.agents/skills/`, the
+ `.claude/skills/` + `.github/skills/` relay pair, and any
+ holdout dir present. There is no skills-dir convention to
+ detect — every target carries `magpie-*` symlinks in the same
+ canonical-plus-relay shape.
## Step 1 — Inventory what was installed
@@ -88,13 +100,13 @@ every artefact).
| Local lock | `<local-lock>` | exists |
| Committed lock | `<committed-lock>` | exists |
| `.gitignore` entries | `<repo-root>/.gitignore` | which of the entries from
[`adopt.md` Step 7](adopt.md) are present |
-| Framework-skill symlinks | `<adopter-skills-dir>/` — both layers under
Pattern B; canonical side only under Pattern D (D.1: `.github/skills/`; D.2:
`.claude/skills/`); single layer under Pattern A | each symlink whose target
resolves into `<snapshot-dir>/skills/` |
+| Framework-skill symlinks | **Every active target dir**
([`agents.md`](agents.md)): the canonical `.agents/skills/` (always present),
the `.claude/skills/` + `.github/skills/` relay pair, and any present holdout
(`.windsurf/skills/`, `.goose/skills/`) | each `magpie-*` symlink — canonical
entries resolving into `<snapshot-dir>/skills/`, relays resolving into
`.agents/skills/magpie-*` — in **each** target dir |
| Post-checkout hook | `<repo-root>/.git/hooks/post-checkout` | exists +
invokes `~/.claude/scripts/sandbox-add-project-root.sh` |
| Doc section: `README.md` | `<repo-root>/README.md` | contains the `##
Agent-assisted contribution (apache-steward)` heading |
| Doc section: `AGENTS.md` | `<repo-root>/AGENTS.md` | contains the `##
apache-steward framework` heading |
| Doc section: `CONTRIBUTING.md` | `<repo-root>/CONTRIBUTING.md` | contains
the adoption section (fallback layout) |
| Overrides directory | `<repo-root>/.apache-magpie-overrides/` | exists;
count framework-scaffold files vs adopter-authored |
-| `setup` skill itself | `<adopter-skills-dir>/magpie-setup/` | exists (this
is the only committed framework skill) |
+| `setup` skill itself | canonical `.agents/skills/magpie-setup/` + the
`.claude`/`.github` relay symlinks to it | exists (this is the only committed
framework skill) |
For the overrides directory: distinguish the
**framework-scaffold** files (`README.md`, `user.md` from
@@ -115,21 +127,24 @@ The following will be REMOVED:
Gitignored (no commit needed):
.apache-magpie/ (snapshot, ~N MB)
.apache-magpie.local.lock
- <adopter-skills-dir>/<symlink-1> → .apache-magpie/skills/<skill-1>/
- <adopter-skills-dir>/<symlink-2> → ...
- .github/skills/<symlink-1> (Pattern B only — second physical
layer)
+ .agents/skills/magpie-<skill-1> → .apache-magpie/skills/<skill-1>/
(canonical)
+ .agents/skills/magpie-<skill-2> → ...
+ .claude/skills/magpie-<skill-1> →
../../.agents/skills/magpie-<skill-1> (relay)
+ .github/skills/magpie-<skill-1> →
../../.agents/skills/magpie-<skill-1> (relay)
+ <holdout>/skills/magpie-<skill-1> →
../../.agents/skills/magpie-<skill-1> (relay; e.g. .windsurf/skills/,
.goose/skills/ — only if present)
.git/hooks/post-checkout (if it contains the steward recipe)
- # Pattern A: <adopter-skills-dir> = .claude/skills/
- # Pattern B: <adopter-skills-dir> spans .claude/skills/ AND
.github/skills/
- # Pattern D: <adopter-skills-dir> = canonical side only
- # (D.1: .github/skills/; D.2: .claude/skills/)
+ # Target dirs (per agents.md): canonical .agents/skills/, the
+ # .claude/skills/ + .github/skills/ relay pair, plus any present
+ # holdout — each carries one magpie-<n> entry per linked skill.
Committed (will show in `git status`):
.apache-magpie.lock (the project's pin)
.gitignore (the entries listed in adopt.md Step
7)
README.md (the `## Agent-assisted contribution
(apache-steward)` section)
AGENTS.md (the `## apache-steward framework`
section, if present)
- <adopter-skills-dir>/magpie-setup/ (this skill itself — self-destructive)
+ .agents/skills/magpie-setup/ (this skill itself —
self-destructive; canonical copy)
+ .claude/skills/magpie-setup (relay symlink)
+ .github/skills/magpie-setup (relay symlink)
The following will be PRESERVED:
@@ -163,12 +178,15 @@ uncommitted edits, prepend a **warning** above the table:
Commit, stash, or copy them out before continuing.
```
-If any framework-skill symlink under `<adopter-skills-dir>/`
-resolves to a path **outside** `<snapshot-dir>/` — i.e. an
-adopter committed a real skill at the same name post-
-adoption, or a symlink points elsewhere — list it under a
-separate **Preserved (not framework-owned)** subsection. The
-unadopt flow never deletes content it does not own.
+If any `magpie-*` symlink in **any active target dir**
+(canonical `.agents/skills/`, a holdout, or the `.claude/`/`.github/`
+relay pair) resolves to a path **outside** the adoption — i.e. a
+canonical entry that does not resolve into `<snapshot-dir>/`, a
+relay that does not resolve through `.agents/skills/`, or an
+adopter who committed a real skill at the same name post-adoption
+— list it under a separate **Preserved (not framework-owned)**
+subsection. The unadopt flow never deletes content it does not
+own.
## Step 3 — Confirm
@@ -191,19 +209,24 @@ artefacts that *depend* on others come out first, so a
half-completed unadopt never leaves a dangling symlink
pointing at a deleted snapshot.
-1. **Framework-skill symlinks.** For each entry in the
- inventory, `rm` the symlink. Per-pattern:
-
- - **Pattern A** — one layer; just remove
- `.claude/skills/magpie-<n>`.
- - **Pattern B** — two layers; remove both
- `.claude/skills/magpie-<n>` and `.github/skills/magpie-<n>`.
- - **Pattern D** — one layer at the canonical side
- (D.1: `.github/skills/magpie-<n>`; D.2: `.claude/skills/magpie-<n>`).
- The directory symlink itself (`.claude/skills` or
- `.github/skills`) is **adopter-owned** and **not
- removed by unadopt** — it predates framework adoption
- and serves the adopter's own native skills too.
+1. **Framework-skill symlinks — in every active target dir.**
+ For each entry in the inventory, `rm` the `magpie-*` symlink.
+ Cover **every active target dir** ([`agents.md`](agents.md)),
+ not just the `.claude/`/`.github/` pair — skipping
+ `.agents/skills/` or a holdout would orphan its `magpie-*`
+ links once the snapshot is removed in step 3.
+
+ - **Canonical target (`.agents/skills/`)** — remove each
+ `.agents/skills/magpie-<n>` (the link into the snapshot).
+ - **Relay targets (`.claude/skills/`, `.github/skills/`, any
+ present holdout)** — remove each `<target>/skills/magpie-<n>`
+ (the relay into `.agents/skills/`).
+
+ The target dirs themselves (`.agents/skills/`, `.claude/skills/`,
+ `.github/skills/`, any holdout) are **adopter-owned** and **not
+ removed by unadopt** — they may predate framework adoption and
+ serve the adopter's own native skills too. Only the `magpie-*`
+ entries come out, never the directory.
Never touch a non-symlink at the same path.
2. **Post-checkout hook.** Remove only if its content matches
@@ -238,9 +261,10 @@ pointing at a deleted snapshot.
7. **Committed lock.** `git rm <committed-lock>`.
8. **Overrides directory** *(only if `--purge-overrides`)*.
`git rm -r .apache-magpie-overrides/`.
-9. **`setup` skill itself.**
- `git rm -r <adopter-skills-dir>/magpie-setup/`. After
- this step the running skill has deleted its own committed
+9. **`setup` skill itself.** `git rm -r` the canonical copy
+ `.agents/skills/magpie-setup/` and its relay symlinks
+ `.claude/skills/magpie-setup` and `.github/skills/magpie-setup`.
+ After this step the running skill has deleted its own committed
source. Future invocations of `/magpie-setup` will
resolve to nothing — the adopter has to re-run the
install recipe in
@@ -257,12 +281,15 @@ After the deletions, verify the post-state:
- `<snapshot-dir>/` does not exist.
- `<committed-lock>` and `<local-lock>` do not exist.
-- No symlinks under `<adopter-skills-dir>/` resolve into
- `<snapshot-dir>/` (the path is gone, so dangling symlinks
- would surface here).
+- No `magpie-*` symlinks remain in **any active target dir**
+ (canonical `.agents/skills/`, the `.claude/`/`.github/` relay
+ pair, or any holdout) — neither dangling canonical links into
+ the removed `<snapshot-dir>/` nor relays into the now-empty
+ `.agents/skills/`.
- `.gitignore` no longer contains the steward entries.
- The doc sections are gone from the affected files.
-- `<adopter-skills-dir>/magpie-setup/` does not exist.
+- `.agents/skills/magpie-setup/` and its `.claude`/`.github`
+ relays do not exist.
- If `--purge-overrides`: `.apache-magpie-overrides/` does
not exist.
- If *not* `--purge-overrides`:
@@ -277,7 +304,7 @@ A summary of what was removed + what remains:
```text
✓ Snapshot removed: .apache-magpie/
✓ Locks removed: .apache-magpie.lock, .apache-magpie.local.lock
-✓ Symlinks removed: <count> (per-pattern — A: under .claude/skills/; B:
under both .claude/skills/ AND .github/skills/; D: under the canonical side
only)
+✓ Symlinks removed: <count> across every active target dir — canonical
.agents/skills/ + the .claude/skills/ + .github/skills/ relay pair + any
present holdout
✓ Post-checkout hook: removed (or: preserved — contained extra adopter
logic)
✓ Doc sections removed: README.md[, AGENTS.md][, CONTRIBUTING.md]
✓ .gitignore cleaned: <N> entries removed
@@ -286,7 +313,7 @@ A summary of what was removed + what remains:
Preserved:
.apache-magpie-overrides/ (M files; pass `--purge-overrides` to remove)
~/.config/apache-magpie/user.md (per-user; shared with other adopters on
this machine — remove manually if this was your last adoption)
- .claude/skills (or .github/skills) (Pattern D directory symlink —
adopter-owned, predates framework adoption)
+ .agents/skills/, .claude/skills/, .github/skills/ (target dirs —
adopter-owned; only the magpie-* entries were removed)
<list of any non-steward-owned content the plan flagged>
Staged for commit (you'll see in `git status`):
@@ -294,7 +321,7 @@ Staged for commit (you'll see in `git status`):
M .gitignore
M README.md
M AGENTS.md (if section was present)
- D <adopter-skills-dir>/magpie-setup/...
+ D .agents/skills/magpie-setup/... (+ .claude/.github relay symlinks)
To re-adopt later: follow docs/setup/install-recipes.md in the
framework repo at https://github.com/apache/airflow-steward.
diff --git a/.github/skills/magpie-setup/upgrade.md
b/.agents/skills/magpie-setup/upgrade.md
similarity index 82%
rename from .github/skills/magpie-setup/upgrade.md
rename to .agents/skills/magpie-setup/upgrade.md
index bc104e10af4..b760a7e84b1 100644
--- a/.github/skills/magpie-setup/upgrade.md
+++ b/.agents/skills/magpie-setup/upgrade.md
@@ -67,7 +67,7 @@ must be migrated by hand.
If you detect **any** legacy artefact here —
`.apache-steward.lock`, `.apache-steward/`,
`.apache-steward-overrides/`, a committed
-`<adopter-skills-dir>/setup-steward/`, or a framework symlink
+`setup-steward/` skill directory, or a framework symlink
**without** the `magpie-` prefix — do **not** continue the normal
upgrade against the half-migrated state. Stop and surface the
manual remediation:
@@ -111,7 +111,7 @@ For each kind of drift, present:
`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/`** —
+- **Files touched in the framework's skill set** —
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).
@@ -171,9 +171,8 @@ project just pinned to, not against the pre-upgrade
bootstrap logic. It implements
[`SKILL.md` Golden rule 9](SKILL.md#golden-rules).
-1. Compute the diff between the adopter-side
- `<adopter-skills-dir>/magpie-setup/` (committed copy)
- and the snapshot's
+1. Compute the diff between the canonical committed copy
+ `.agents/skills/magpie-setup/` and the snapshot's
`.apache-magpie/skills/setup/`.
2. **If the adopter has local modifications** to their
committed copy beyond what the snapshot ships — surface
@@ -187,37 +186,22 @@ bootstrap logic. It implements
over the committed copy:
```bash
- # For the flat layout (Pattern A):
- rm -rf .claude/skills/magpie-setup
+ # Overwrite the canonical committed copy:
+ rm -rf .agents/skills/magpie-setup
cp -r .apache-magpie/skills/setup \
- .claude/skills/magpie-setup
-
- # For the double-symlinked layout (Pattern B):
- rm -rf .github/skills/magpie-setup
- cp -r .apache-magpie/skills/setup \
- .github/skills/magpie-setup
- # The .claude/skills/magpie-setup per-skill symlink does
- # not need touching — it points at .github/skills/magpie-setup
- # which is now the new content.
-
- # For the single directory-symlink layout (Pattern D),
- # write to the *canonical* side only. With D.1
- # (canonical = .github/skills/):
- rm -rf .github/skills/magpie-setup
- cp -r .apache-magpie/skills/setup \
- .github/skills/magpie-setup
- # With D.2 (canonical = .claude/skills/), write to
- # .claude/skills/magpie-setup instead. Either way: the
- # symlinked side resolves to the refreshed content
- # automatically — nothing to touch there.
+ .agents/skills/magpie-setup
+ # The relay symlinks (.claude/skills/magpie-setup,
+ # .github/skills/magpie-setup) point at
+ # ../../.agents/skills/magpie-setup and resolve to the
+ # refreshed content automatically — nothing to touch there.
```
4. **Reload in-flight.** Immediately after the copy lands —
before doing anything else in this run — re-read the
- updated `<adopter-skills-dir>/magpie-setup/SKILL.md`,
+ updated `.agents/skills/magpie-setup/SKILL.md`,
the just-overwritten `upgrade.md` (this file), and any
helper file you have already opened in this run
- (`conventions.md`, `overrides.md`, `verify.md`). Resume
+ (`agents.md`, `overrides.md`, `verify.md`). Resume
the upgrade from the step *after* this one, executing
the reloaded content — not the version of this file
that was in memory when the upgrade started.
@@ -258,6 +242,25 @@ pattern-matching.
## Step 6 — Refresh framework-skill symlinks
+This step refreshes symlinks for **every active target dir**
+([`agents.md`](agents.md)), not just the `.claude/`/`.github/`
+pair. Compute the **active target set** the same way `adopt`
+does: the always-on neutral targets `.agents/skills/`
+(`universal` — the path shared by Codex, Cursor, Gemini CLI,
+Copilot, OpenCode, …), `.claude/skills/` (`claude-code`), and
+`.github/skills/` (`github`), **plus any registry holdout
+already present in the repo** (`.windsurf/skills/`,
+`.goose/skills/`, …). When the framework has added a new
+always-on target since the last run, it joins the active set and
+gets its symlinks created on this upgrade — the same way the
+effective family set below picks up newly-introduced families.
+Wiring is the **canonical-plus-relay** model
+([`agents.md`](agents.md)), applied identically regardless of any
+pre-existing `.claude/`/`.github/` layout: `.agents/skills/` holds
+the canonical `magpie-<n>` → snapshot links; every other active
+target gets relay `magpie-<n>` → `../../.agents/skills/magpie-<n>`
+links.
+
Read the opt-in skill families from `<committed-lock>`
(falling back to `<local-lock>` if the committed lock is
silent on families). Compose the **effective family set**
@@ -293,76 +296,85 @@ a new `setup-*` or `list-*` skill in a release, and
contracts on a rename / removal without code changes here.
Before creating symlinks for a newly-introduced opt-in
-family, reconcile the adopter's `.gitignore` so the new
-family's snapshot symlinks are gitignored. Append the
-`.gitignore` lines from
+family — or for a newly-present active target dir — reconcile
+the adopter's `.gitignore` so the new snapshot symlinks are
+gitignored. Append the `.gitignore` lines from
[`adopt.md` Step 7](adopt.md#step-7--gitignore-entries-fresh-only)
-for the new family's prefix, matching the adopter's
-[skills-dir convention](conventions.md):
-
-- Pattern A — `/.claude/skills/<prefix>-*` only.
-- Pattern B — both `/.claude/skills/<prefix>-*` and
- `/.github/skills/<prefix>-*` (two physical symlinks per
- skill).
-- Pattern D — only the *canonical-side* `<canonical>/<prefix>-*`
- ignore line. D.1 → `/.github/skills/<prefix>-*`; D.2 →
- `/.claude/skills/<prefix>-*`. The symlinked side's
- directory symlink does not need its own ignore line — git
- does not descend into it.
+for **each active target dir** ([`agents.md`](agents.md)). Every
+framework skill is symlinked under the `magpie-` prefix, so a
+single `magpie-*` glob (plus the `!…/magpie-setup` negation that
+keeps the committed bootstrap tracked) covers them all per
+target — no per-family lines:
+
+One **uniform** two-line block per active target dir (canonical
+and relays alike), no per-layout variation:
+
+```text
+/.agents/skills/magpie-*
+!/.agents/skills/magpie-setup
+/.claude/skills/magpie-*
+!/.claude/skills/magpie-setup
+/.github/skills/magpie-*
+!/.github/skills/magpie-setup
+```
+
+Add the analogous two lines for any present holdout
+(`.windsurf/skills/`, `.goose/skills/`, …).
The append is idempotent — skip lines that already exist.
The same idempotence covers adopters whose `.gitignore`
already had the entries (e.g. from a manually-edited block
or a previous adopt run).
-The post-upgrade state must be: *every framework skill in
-the new snapshot that belongs to the effective family set
-has a valid symlink in `<adopter-skills-dir>`*, and *no
-symlink points at a framework skill that no longer exists
-in the snapshot*.
+The post-upgrade state must be: *in every active target dir,
+every framework skill in the new snapshot that belongs to the
+effective family set has a valid symlink*, and *no symlink (in
+any target dir) points at a framework skill that no longer
+exists in the snapshot*.
-Run two passes:
+Run two passes **per active target dir** ([`agents.md`](agents.md)):
1. **Ensure every family-member skill is linked.** For each
framework skill in the new snapshot that belongs to the
- effective family set, check
- `<adopter-skills-dir>/magpie-<skill>`:
- - If the symlink exists and points at the matching
- snapshot path, leave it alone.
+ effective family set, check `<target>/magpie-<skill>` in each
+ active target dir (`.agents/skills/`, `.claude/skills/`,
+ `.github/skills/`, plus any present holdout):
+ - If the symlink exists and points at the expected target
+ for that dir (canonical → snapshot; relay →
+ `../../.agents/skills/magpie-<skill>`), leave it alone.
- If it's missing, create it.
- If it exists but is broken (target gone, points at the
wrong path), repair it.
Do this unconditionally — do not skip skills whose
symlinks "should" already be there. A contributor who
- ran `git clean -fdx`, blew away `<adopter-skills-dir>` by
+ ran `git clean -fdx`, blew away a target dir by
accident, or merged a branch that removed the symlinks
- gets the full set restored without per-symlink re-
- prompting. The aggregated list of created / repaired
- links is reported in the upgrade summary (Step 8 output
- block, under the `+` and `↻` rows).
-
-2. **Reconcile stale symlinks.** Walk
- `<adopter-skills-dir>` looking for symlinks that point
- at framework skills no longer in the new snapshot
- (rename, removal). For each:
+ gets the full set restored in **every** target without
+ per-symlink re-prompting. The aggregated list of created /
+ repaired links is reported in the upgrade summary (Step 8
+ output block, under the `+` and `↻` rows). A newly-present
+ target dir (a holdout that just appeared, or a new always-on
+ target the framework added) gets its full set created here.
+
+2. **Reconcile stale symlinks.** Walk **each active target
+ dir** looking for symlinks that point at framework skills no
+ longer in the new snapshot (rename, removal). For each:
- 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.
-Per-pattern symlink layers to refresh:
+Per-target symlink layers to refresh:
-- **Pattern A (flat)** — refresh the single layer at
- `.claude/skills/magpie-<n>`.
-- **Pattern B (double-symlinked)** — refresh both layers
- (inner at `.github/skills/magpie-<n>`, outer at
- `.claude/skills/magpie-<n>` → inner).
-- **Pattern D (single directory symlink)** — refresh only
- the *canonical-side* layer at
- `<canonical-side>/magpie-<n>` (D.1 → `.github/skills/magpie-<n>`;
- D.2 → `.claude/skills/magpie-<n>`). The symlinked-side path
- resolves through the directory symlink and needs no
- per-skill plumbing.
+- **Canonical target (`.agents/skills/`)** — refresh the
+ canonical layer at `.agents/skills/magpie-<n>` →
+ `../../.apache-magpie/skills/<n>/`.
+- **Relay targets (`.claude/skills/`, `.github/skills/`, any
+ present holdout)** — refresh the relay layer at
+ `<target>/skills/magpie-<n>` → `../../.agents/skills/magpie-<n>`.
+ Repair a relay if it is missing, broken, or still points at the
+ old snapshot path directly (a pre-canonical-model layout) rather
+ than at the canonical `.agents/skills/` entry.
## Step 6b — Sync locally-installed hooks and configuration
@@ -404,12 +416,12 @@ case (e.g. a contributor accidentally edited
## Step 6c — Propagate to every worktree (run `worktree-init` unconditionally)
The main checkout drives the upgrade, but each worktree
-carries its own gitignored `<adopter-skills-dir>` symlinks.
-Those symlinks need refreshing too — otherwise a developer
-sitting in a worktree sees the new snapshot via the shared
-`<snapshot-dir>` symlink (per
-[`worktree-init.md`](worktree-init.md)) but their
-`<adopter-skills-dir>` may still point at *missing* skills
+carries its own gitignored canonical + relay framework-skill
+symlinks. Those symlinks need refreshing too — otherwise a
+developer sitting in a worktree sees the new snapshot via the
+shared `<snapshot-dir>` symlink (per
+[`worktree-init.md`](worktree-init.md)) but their per-target
+`magpie-*` symlinks may still point at *missing* skills
(a family the upgrade added) or *renamed* ones (a framework
rename).
@@ -435,8 +447,8 @@ Procedure:
committed lock the worktree shares via git) plus the
always-on families per
[`SKILL.md` Golden rule 8](SKILL.md#golden-rules), and
- reconciles both the snapshot symlink and the
- `<adopter-skills-dir>` symlinks (see
+ reconciles both the snapshot symlink and the canonical +
+ relay framework-skill symlinks (see
[`worktree-init.md` Step 1 + Step 1b](worktree-init.md)).
3. Collect each invocation's recap into a per-worktree row
for the upgrade summary's `Worktrees:` section
@@ -492,7 +504,7 @@ committed project-scope file). Idempotent — already-present
paths are skipped. If
`~/.claude/scripts/sandbox-add-project-root.sh` is absent,
surface as ⚠ in the upgrade summary with a pointer at
-`/setup-isolated-setup-install` and continue (do not block
+`/magpie-setup-isolated-setup-install` and continue (do not block
upgrade — secure-agent setup is independent of framework
upgrade). The recap row in Step 8's output goes under a new
`Sandbox allowlist:` section.
@@ -635,15 +647,16 @@ Symlinks (main checkout):
Always-on families: setup-*, list-* (per Golden rule 8)
✓ <list of unchanged symlinks>
+ <list of newly-created symlinks (skill present in the
- effective family set but missing from <adopter-skills-dir>)>
+ effective family set but missing from an active target dir)>
↻ <list of repaired symlinks (existed but broken / pointing
at the wrong path)>
- <list of removed stale symlinks>
.gitignore reconcile:
- ✓ all opt-in family prefixes already gitignored OR
- + <list of /.claude/skills/<prefix>-* and /.github/skills/<prefix>-*
- lines appended for newly-introduced opt-in families>
+ ✓ all active-target magpie-* globs already gitignored OR
+ + <list of /.agents/skills/magpie-*, /.claude/skills/magpie-*,
+ /.github/skills/magpie-* (+ any holdout) lines appended for
+ newly-introduced families or newly-present target dirs>
Hooks + local config:
✓ <list of files in sync>
@@ -659,7 +672,7 @@ Worktrees (worktree-init was run on each, idempotently):
Sandbox allowlist (sandbox-add-project-root.sh --all-worktrees):
✓ already covers this project + N worktrees OR
+ <list of <worktree>/.claude/settings.local.json files updated> OR
- ⚠ helper not installed — run /setup-isolated-setup-install
+ ⚠ helper not installed — run /magpie-setup-isolated-setup-install
Overrides:
✓ <list of overrides whose target is unchanged>
@@ -673,7 +686,7 @@ Framework templates (projects/_template/):
→ file an issue against apache/airflow-steward to upstream a fix
Recommended follow-ups:
- - Run /setup-isolated-setup-update if the secure-setup blast
+ - Run /magpie-setup-isolated-setup-update if the secure-setup blast
radius (settings.json, agent-isolation/, pinned-versions.toml)
appears in the diff.
- Open .apache-magpie-overrides/<name>.md for any ⚠ entry above.
diff --git a/.github/skills/magpie-setup/verify.md
b/.agents/skills/magpie-setup/verify.md
similarity index 88%
rename from .github/skills/magpie-setup/verify.md
rename to .agents/skills/magpie-setup/verify.md
index 8db9c41ec17..1c634d63c10 100644
--- a/.github/skills/magpie-setup/verify.md
+++ b/.agents/skills/magpie-setup/verify.md
@@ -12,8 +12,11 @@ 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
+ snapshot is present but symlinks are missing or dangling
+ in **any active target dir** ([`agents.md`](agents.md) —
+ `.agents/skills/`, `.claude/skills/`, `.github/skills/`, plus
+ any present holdout), recreate them across all of them. Used
+ by the post-checkout hook
([`adopt.md` Step 10](adopt.md)) on a fresh worktree
where the gitignored symlinks didn't follow the
checkout.
@@ -46,21 +49,28 @@ adoption state.
1. **Marker lock.** `.apache-magpie.lock` parses and records
`method: local`. ✓ when present; ✗ with a pointer at
`/magpie-setup` otherwise.
-2. **Symlinks resolve into `skills/`.** In **both**
- `.claude/skills/` and `.github/skills/`, every `magpie-<n>` is
- a symlink whose target (`../../skills/<n>/`) resolves to a
- directory containing `SKILL.md`. ✗ list any dangling or
- non-symlink entry; remediation: re-run `/magpie-setup`
- (idempotent).
+2. **Symlinks resolve (canonical → source, relays → canonical).**
+ In the canonical dir `.agents/skills/`, every `magpie-<n>` is a
+ symlink whose target (`../../skills/<n>/`) resolves to a
+ directory containing `SKILL.md`. In every **relay** target dir
+ ([`agents.md`](agents.md) — `.claude/skills/`, `.github/skills/`,
+ plus any present holdout), every `magpie-<n>` is a symlink whose
+ target (`../../.agents/skills/magpie-<n>`) resolves through the
+ canonical entry to the same `SKILL.md`. ✗ list any dangling or
+ non-symlink entry — or any relay that points straight at the
+ snapshot/source instead of at `.agents/skills/` — naming the
+ target dir; remediation: re-run `/magpie-setup` (idempotent).
3. **Coverage.** Every `skills/<n>/` with a `SKILL.md` has a
- matching `magpie-<n>` symlink in **both** dirs (unless a
- `skill-families:` filter was deliberately applied). ⚠ list any
- source skill with no link; remediation: `/magpie-setup`.
-4. **`.gitignore`.** `.claude/skills/*` and `.github/skills/*` are
- ignored, with `!/.claude/skills/magpie-*` and
- `!/.github/skills/magpie-*` un-ignoring the committed symlinks.
- ✗ if either un-ignore line is missing (those symlinks would not
- be tracked).
+ canonical `magpie-<n>` symlink in `.agents/skills/` and a
+ matching relay in **every other active target dir**
+ (unless a `skill-families:` filter was deliberately applied).
+ ⚠ list any source skill with no link, per target; remediation:
+ `/magpie-setup`.
+4. **`.gitignore`.** Each active target dir's `<dir>/*` is
+ ignored, with `!/<dir>/magpie-*` un-ignoring the committed
+ symlinks — `.agents/skills/`, `.claude/skills/`,
+ `.github/skills/`, and any present holdout. ✗ if any un-ignore
+ line is missing (those symlinks would not be tracked).
5. **No remote leftovers.** No `.apache-magpie/` snapshot dir and
no `.apache-magpie.local.lock` — local self-adoption uses
neither. ⚠ surface either if found (a stale remote adoption was
@@ -150,21 +160,19 @@ Check that the entries from
must never be committed since the content is machine-specific
absolute paths)
-Recommended (the family patterns the adopter's
-[skills-dir convention](conventions.md) requires):
-
-- **Pattern A** — framework-skill symlink patterns
- (`security-*`, `pr-management-*`, `issue-*`,
- `setup-isolated-setup-*`, `setup-shared-config-sync`,
- `list-*`) under `.claude/skills/` only.
-- **Pattern B** — same patterns under **both**
- `.claude/skills/` and `.github/skills/` (one ignore line
- per physical symlink).
-- **Pattern D** — same patterns under the **canonical side
- only** (`.github/skills/` for D.1; `.claude/skills/` for
- D.2). The symlinked side does not need its own ignore
- lines because git does not descend into a directory
- symlink.
+Recommended (a **uniform** `magpie-*` glob block per **active
+target dir** — [`agents.md`](agents.md) — with no per-layout
+variation):
+
+- **Canonical target (`.agents/skills/`)** — always present:
+ `/.agents/skills/magpie-*` with `!/.agents/skills/magpie-setup`.
+- **Relay targets (`.claude/skills/`, `.github/skills/`)** — the
+ same two-line block keyed on each dir
+ (`/.claude/skills/magpie-*` with `!/.claude/skills/magpie-setup`,
+ and likewise for `.github/skills/`).
+- **Any present holdout** (`.windsurf/skills/`,
+ `.goose/skills/`, …) — the same two-line block keyed on its own
+ dir.
- ✗ if `/.apache-magpie/` is not gitignored — the snapshot
is at risk of being accidentally committed.
@@ -180,16 +188,27 @@ Recommended (the family patterns the adopter's
### 5. Symlinks point at live framework skills
-For each symlink under `<adopter-skills-dir>` that resolves
-into `.apache-magpie/skills/<name>/`:
+Run this check across **every active target dir**
+([`agents.md`](agents.md) — `.agents/skills/`, `.claude/skills/`,
+`.github/skills/`, plus any present holdout), not just the
+`.claude/`/`.github/` pair.
+
+For each `magpie-*` symlink under any active target dir —
+canonical ones resolving (via `.agents/skills/`) into
+`.apache-magpie/skills/<name>/`, relays resolving through
+`../../.agents/skills/magpie-<name>` to the same:
-- ✓ if the target exists.
-- ✗ if dangling (target deleted or snapshot missing).
- Remediation: `/magpie-setup adopt` (idempotent re-run)
- or this same skill with `--auto-fix-symlinks`.
+- ✓ if it resolves to a live skill.
+- ✗ if dangling (target deleted or snapshot missing), or a relay
+ pointing straight at the snapshot instead of at the canonical
+ `.agents/skills/` entry, naming the target dir. Remediation:
+ `/magpie-setup adopt` (idempotent re-run) or this same skill
+ with `--auto-fix-symlinks`.
For each framework skill in the snapshot **not** symlinked
-in the adopter, classify it:
+in a given active target dir, classify it (a skill missing
+from `.agents/skills/` is as much a gap as one missing from
+`.claude/skills/`):
- **Always-on family** (every `setup-*` *except*
`setup` itself, and every `list-*` — per
@@ -208,9 +227,9 @@ in the adopter, classify it:
family; the warning prompts a decision.
The `--auto-fix-symlinks` path repairs the first two
-classes in place without prompting; the ⚠ class needs an
-explicit `/magpie-setup adopt` re-run with the family
-added to the pick.
+classes in place — in **every active target dir** — without
+prompting; the ⚠ class needs an explicit `/magpie-setup adopt`
+re-run with the family added to the pick.
### 6. `.apache-magpie-overrides/` exists + has the README
@@ -226,8 +245,8 @@ with the `README.md` scaffold from
### 7. The `setup` skill itself is up to date
-Compare the adopter-side committed `setup` skill
-(at `<adopter-skills-dir>/magpie-setup/`) against the
+Compare the canonical committed `setup` skill
+(at `.agents/skills/magpie-setup/`) against the
snapshot's `.apache-magpie/skills/setup/`.
- ✓ if same content.
@@ -311,13 +330,13 @@ For the current worktree (resolved via
pass.
- ⚠ if missing from either array **and** the helper script is
absent — the operator has not run
- `/setup-isolated-setup-install` yet. Suggest that skill.
+ `/magpie-setup-isolated-setup-install` yet. Suggest that skill.
Not ✗ because secure-agent isolation is independent of
framework adoption, and an adopter who runs without the
sandbox enabled has nothing to lose by the missing entry.
- ⚠ if `<worktree>/.claude/settings.local.json` is absent
entirely — same remediation (re-run the helper or
- `/setup-isolated-setup-install`). The file is auto-created
+ `/magpie-setup-isolated-setup-install`). The file is auto-created
by the helper on first run.
- ✗ if `<worktree>/.claude/settings.local.json` exists AND
is **not** gitignored (cross-check via `git check-ignore`).
@@ -550,7 +569,7 @@ which holds a POSIX `fcntl.flock` advisory exclusive lock on
the target file, re-parses under the lock, mutates
`.permissions.allow[]` in place, writes to a sibling temp
file, and `os.replace`s into place — so concurrent
-`/setup-isolated-setup-install` (which also writes to the same
+`/magpie-setup-isolated-setup-install` (which also writes to the same
file's `sandbox.filesystem.*` arrays) does not silently
clobber the diff. When the target file lives at a path the
agent's sandbox marks as `denyWithinAllow` (the per-machine
@@ -559,7 +578,7 @@ operator to authorise the sandbox bypass for that single
write
— it does not silently skip the file. ⚠ if either file is
absent (most adopters will have at least
`settings.local.json` after the first
-`/setup-isolated-setup-install` pass; absence is a soft signal
+`/magpie-setup-isolated-setup-install` pass; absence is a soft signal
not a hard fault).
**Why we propose, never auto-apply.** The allow-list is
diff --git a/.github/skills/magpie-setup/worktree-init.md
b/.agents/skills/magpie-setup/worktree-init.md
similarity index 74%
rename from .github/skills/magpie-setup/worktree-init.md
rename to .agents/skills/magpie-setup/worktree-init.md
index c4a2240a7a8..07fcf28bb9c 100644
--- a/.github/skills/magpie-setup/worktree-init.md
+++ b/.agents/skills/magpie-setup/worktree-init.md
@@ -70,13 +70,16 @@ Then verify the chain end-to-end:
- `ls <worktree>/.apache-magpie/skills/` lists the
same skills as `ls <main>/.apache-magpie/skills/`.
-## Step 1b — Wire up the worktree's `<adopter-skills-dir>` symlinks
+## Step 1b — Wire up the worktree's per-target symlinks
The snapshot symlink in Step 1 only makes the framework's
-*source* available to this worktree. The `<adopter-skills-dir>`
-symlinks (the gitignored per-skill entries the agent harness
-actually resolves) are **per-worktree** — each working copy
-needs its own. A worktree branched from before adoption
+*source* available to this worktree. The per-skill symlinks (the
+gitignored entries the agent harness actually resolves) live in
+**every active target dir** ([`agents.md`](agents.md) —
+`.agents/skills/` (universal), `.claude/skills/`,
+`.github/skills/`, plus any present holdout) and are
+**per-worktree** — each working copy needs its own in every
+target. A worktree branched from before adoption
landed, or branched from a state where the symlinks were
cleaned, has none on disk.
@@ -91,44 +94,40 @@ Compose the **effective family set** for this worktree:
[`SKILL.md` Golden rule 8](SKILL.md#golden-rules). These
are added unconditionally, never read from the lock.
-For each framework skill in the effective family set:
-
-- If `<worktree>/<adopter-skills-dir>/magpie-<skill>` is missing —
- create it (gitignored).
-- If it exists and points at the correct snapshot path —
- leave it alone.
-- If it exists but is broken or points at the wrong path —
- repair it.
-
-Reuse the convention detection from
-[`conventions.md`](conventions.md). The pattern drives how
-many layers the worktree's `<adopter-skills-dir>` needs:
-
-- **Pattern A (flat)** — one layer at
- `.claude/skills/magpie-<n>`.
-- **Pattern B (double-symlinked)** — two layers (inner at
- `.github/skills/magpie-<n>`, outer at `.claude/skills/magpie-<n>` →
- inner). Both gitignored.
-- **Pattern D (single directory symlink)** — one layer at
- the canonical side (D.1: `.github/skills/magpie-<n>`;
- D.2: `.claude/skills/magpie-<n>`). The symlinked side resolves
- automatically through the directory symlink, so there is
- no per-skill plumbing to add or repair on that side.
-
-The worktree's `.claude/skills` / `.github/skills` directory
-symlink itself (for Pattern D) is **not** a framework
-artefact — it is checked into the repo as part of the
-adopter's layout, so every worktree inherits it via the
-ordinary `git worktree add` flow. `worktree-init` does not
-touch it.
-
-Pick any framework skill symlink that should now exist (e.g.
-`<worktree>/.claude/skills/magpie-security-issue-sync/SKILL.md`) and
-confirm `readlink -f` resolves it into
+Wiring follows the **canonical-plus-relay** model
+([`agents.md`](agents.md)), with no per-layout variation. For each
+framework skill in the effective family set:
+
+- **Canonical (`.agents/skills/`)** — ensure
+ `<worktree>/.agents/skills/magpie-<skill>` →
+ `../../.apache-magpie/skills/<skill>/` (the worktree's snapshot
+ symlink from Step 1). Create if missing, repair if broken or
+ pointing at the wrong path, leave alone if correct.
+- **Relays (`.claude/skills/`, `.github/skills/`, any present
+ holdout)** — ensure `<worktree>/<target>/skills/magpie-<skill>`
+ → `../../.agents/skills/magpie-<skill>`. Create / repair / leave
+ alone the same way.
+
+All these entries are gitignored and per-worktree.
+
+The worktree's target directories themselves — `.agents/skills/`,
+`.claude/skills/`, `.github/skills/`, any holdout — are **not**
+framework artefacts; they are checked into the repo as part of the
+adopter's layout, so every worktree inherits them via the
+ordinary `git worktree add` flow. `worktree-init` only wires the
+`magpie-*` entries inside them; it does not touch the
+directories.
+
+Pick a framework skill symlink that should now exist in **each**
+active target dir (e.g.
+`<worktree>/.agents/skills/magpie-security-issue-sync/SKILL.md`
+and `<worktree>/.claude/skills/magpie-security-issue-sync/SKILL.md`)
+and confirm `readlink -f` resolves each into
`<main>/.apache-magpie/...` rather than dangling — same
sanity check as Step 1's bottom bullet, just now end-to-end
from agent-harness path through the worktree's symlink
-through the snapshot symlink to the framework source.
+through the snapshot symlink to the framework source, in every
+target.
## Step 1c — Add the worktree to its own project-local sandbox allowlists
@@ -169,7 +168,7 @@ one-line recap row for the Step 2 summary:
- ✓ already covered, OR
- + added `<worktree-path>`, OR
-- ⚠ helper not installed — `/setup-isolated-setup-install` to wire it up.
+- ⚠ helper not installed — `/magpie-setup-isolated-setup-install` to wire it
up.
`worktree-init` does **not** fail when the helper is absent;
secure-agent isolation is independent of framework adoption.
@@ -182,9 +181,10 @@ Print a short summary:
- The main checkout's resolved path.
- The framework version the main is pinned at (read from
`<main>/.apache-magpie.lock`).
-- The effective family set wired in Step 1b, split into
- *opt-in* and *always-on*, with per-skill ✓ / + / ↻
- counts.
+- The effective family set wired in Step 1b across every
+ active target dir (`.agents/skills/`, the `.claude/`/`.github/`
+ pair, any present holdout), split into *opt-in* and
+ *always-on*, with per-skill ✓ / + / ↻ counts.
- A reminder: `upgrade` from the main, not from the worktree.
## Inputs
@@ -218,5 +218,5 @@ different overrides. Symlinking it would conflate branches.
| Symptom | Likely cause | Fix |
|---|---|---|
| Step 0 step 3 stops with "main checkout not adopted" | The main has never
run `adopt`. | `cd <main> && /magpie-setup`, then re-run `worktree-init` here. |
-| `worktree-init` runs but skills still fail to resolve | The
`<adopter-skills-dir>/magpie-<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. |
+| `worktree-init` runs but skills still fail to resolve | The per-target
`magpie-<skill>` symlinks (in `.agents/skills/`, the `.claude/`/`.github/`
pair, or a holdout) 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-magpie.bak.<timestamp>` for
any non-snapshot content before deleting. |
diff --git a/.github/skills/prepare-providers-documentation/SKILL.md
b/.agents/skills/prepare-providers-documentation/SKILL.md
similarity index 100%
rename from .github/skills/prepare-providers-documentation/SKILL.md
rename to .agents/skills/prepare-providers-documentation/SKILL.md
diff --git a/.claude/skills/aip-user-stories b/.claude/skills/aip-user-stories
index 801b62bc706..06986229dc1 120000
--- a/.claude/skills/aip-user-stories
+++ b/.claude/skills/aip-user-stories
@@ -1 +1 @@
-../../.github/skills/aip-user-stories
\ No newline at end of file
+../../.agents/skills/aip-user-stories
\ No newline at end of file
diff --git a/.claude/skills/airflow-translations
b/.claude/skills/airflow-translations
new file mode 120000
index 00000000000..774fda334f5
--- /dev/null
+++ b/.claude/skills/airflow-translations
@@ -0,0 +1 @@
+../../.agents/skills/airflow-translations
\ No newline at end of file
diff --git a/.claude/skills/magpie-setup b/.claude/skills/magpie-setup
index d7a9cac8b0f..625c6aa3d47 120000
--- a/.claude/skills/magpie-setup
+++ b/.claude/skills/magpie-setup
@@ -1 +1 @@
-../../.github/skills/magpie-setup
\ No newline at end of file
+../../.agents/skills/magpie-setup
\ No newline at end of file
diff --git a/.claude/skills/prepare-providers-documentation
b/.claude/skills/prepare-providers-documentation
new file mode 120000
index 00000000000..1eadc05fae1
--- /dev/null
+++ b/.claude/skills/prepare-providers-documentation
@@ -0,0 +1 @@
+../../.agents/skills/prepare-providers-documentation
\ No newline at end of file
diff --git a/.github/skills/aip-user-stories b/.github/skills/aip-user-stories
new file mode 120000
index 00000000000..06986229dc1
--- /dev/null
+++ b/.github/skills/aip-user-stories
@@ -0,0 +1 @@
+../../.agents/skills/aip-user-stories
\ No newline at end of file
diff --git a/.github/skills/airflow-translations
b/.github/skills/airflow-translations
new file mode 120000
index 00000000000..774fda334f5
--- /dev/null
+++ b/.github/skills/airflow-translations
@@ -0,0 +1 @@
+../../.agents/skills/airflow-translations
\ No newline at end of file
diff --git a/.github/skills/magpie-setup b/.github/skills/magpie-setup
new file mode 120000
index 00000000000..625c6aa3d47
--- /dev/null
+++ b/.github/skills/magpie-setup
@@ -0,0 +1 @@
+../../.agents/skills/magpie-setup
\ No newline at end of file
diff --git a/.github/skills/magpie-setup/conventions.md
b/.github/skills/magpie-setup/conventions.md
deleted file mode 100644
index 896e397c93e..00000000000
--- a/.github/skills/magpie-setup/conventions.md
+++ /dev/null
@@ -1,278 +0,0 @@
-<!-- SPDX-License-Identifier: Apache-2.0
- https://www.apache.org/legal/release-policy.html -->
-
-# conventions — auto-detect the adopter's skills-dir layout
-
-Different ASF projects already organise their `.claude/skills/`
-differently. Before `setup adopt` creates symlinks
-into the snapshot, it 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.
-
-## Patterns
-
-### A. Flat — skills live directly in `.claude/skills/`
-
-```text
-<repo-root>/
-└── .claude/
- └── skills/
- └── <skill-name>/
- └── SKILL.md
-```
-
-The simple, default Claude Code layout. Most repos that just
-started using Claude Code use this. **Detection signal**:
-`.claude/skills/<n>/SKILL.md` is a regular file.
-
-For framework symlinks: create them at
-`<repo-root>/.claude/skills/magpie-<n>` → relative path into
-`.apache-magpie/skills/<n>/` (the symlink carries the
-`magpie-` prefix; its target keeps the snapshot's clean
-source name).
-
-**Caveat — `.claude/` already gitignored.** Some adopters (notably
-those that previously used Claude Code with per-user `.claude/`
-settings) have `.claude/` listed in their repo's `.gitignore`.
-This prevents `.claude/skills/magpie-setup/` from being committed
-per [`SKILL.md` Golden rule 6](SKILL.md#golden-rules) (which expects
-`setup` itself to be the only committed framework skill).
-
-Three resolution paths:
-
-- **Override the gitignore** — add `!/.claude/skills/magpie-setup/`
- after the broader `.claude/` line. Keeps the rest of `.claude/`
- gitignored; commits only the framework's bootstrap skill.
-- **Switch to Pattern B** — move skills to `.github/skills/` and use
- the double-symlinked layout. Sidesteps the `.claude/` gitignore
- entirely. See *B. Double-symlinked* below.
-- **Defer commit** — treat the whole adoption as a no-commit local
- experiment until the gitignore is revised. Useful for spike-style
- evaluation.
-
-The adopt flow surfaces the conflict when it detects `.claude/` is
-gitignored AND Pattern A was selected; the user picks one of the
-three above.
-
-### B. Double-symlinked — `.claude/skills/` mirrors `.github/skills/`
-
-```text
-<repo-root>/
-├── .claude/
-│ └── skills/
-│ └── <skill-name> → ../../.github/skills/<skill-name>/
-└── .github/
- └── skills/
- └── <skill-name>/
- └── SKILL.md
-```
-
-The pattern apache/airflow uses today (e.g. apache/airflow has this): actual
skill content
-lives under `.github/skills/`; `.claude/skills/<n>` is a
-relative symlink pointing into `.github/skills/<n>/`. The
-rationale (per the airflow team): `.github/` is the canonical
-infra-glue directory in apache/airflow (e.g. that project), and `.claude/` is a
-view of those skills filtered for Claude Code.
-
-**Detection signal**: at least one entry in `.claude/skills/`
-is a symlink resolving into `.github/skills/`.
-
-For framework symlinks: create *both* layers — the inner
-`.github/skills/magpie-<n>` → relative path into
-`.apache-magpie/skills/<n>/`, and the outer
-`.claude/skills/magpie-<n>` → `../../.github/skills/magpie-<n>/`
-(matching the existing pattern). Both layers gitignored.
-
-### C. None yet — neither directory exists
-
-A new adopter that has never used Claude Code skills before.
-The skill creates the directory layout the adopter prefers
-(default: pattern A, flat — simpler). If the user has a
-preference, they say so during the adopt flow.
-
-### D. Single directory symlink — one of `.claude/skills` / `.github/skills`
is a symlink to the other
-
-```text
-# D.1 — content under .github/skills/, .claude/skills is the symlink:
-<repo-root>/
-├── .claude/
-│ └── skills → ../.github/skills/
-└── .github/
- └── skills/
- ├── <native-skill>/
- │ └── SKILL.md
- ├── <framework-symlink> →
../../.apache-magpie/skills/<framework-skill>/
- └── ...
-```
-
-```text
-# D.2 — content under .claude/skills/, .github/skills is the symlink:
-<repo-root>/
-├── .claude/
-│ └── skills/
-│ ├── <native-skill>/
-│ │ └── SKILL.md
-│ ├── <framework-symlink> →
../../.apache-magpie/skills/<framework-skill>/
-│ └── ...
-└── .github/
- └── skills → ../.claude/skills/
-```
-
-A simplification of Pattern B: instead of one per-skill
-symlink mirroring every entry from one directory to the
-other, **one of the two directories is itself a symlink to
-the other**. Both `.claude/skills/<n>` and
-`.github/skills/<n>` always resolve to the same content for
-every skill — the project's native skills and the framework's
-gitignored symlinks alike — without any per-skill plumbing.
-Adding a new skill (project-native or framework) just means
-adding it once in the canonical directory; the mirror is
-automatic.
-
-**Two orientations** — same shape, opposite direction:
-
-- **D.1** — content lives under `.github/skills/`,
- `.claude/skills` is the symlink. The natural choice for
- projects whose canonical skills directory is `.github/`
- (e.g. apache/airflow, which uses `.github/` as its
- infra-glue root and `.claude/` as a Claude-Code-facing
- view).
-- **D.2** — content lives under `.claude/skills/`,
- `.github/skills` is the symlink. The natural choice for
- projects whose canonical skills directory is `.claude/`
- (e.g. a Pattern A project that wants `.github/skills/`
- available too without duplicating content).
-
-**Detection signal**: exactly one of `.claude/skills` /
-`.github/skills` is a symlink (test with `[ -L <path> ]` /
-`readlink <path>`) and resolves to the other path in the same
-repo. Either orientation counts as Pattern D.
-
-For framework symlinks: create them at **only one layer** —
-the *real* directory side, never the symlinked side. With
-D.1 that means `.github/skills/magpie-<n>` → relative path into
-`.apache-magpie/skills/<n>/`; with D.2 it means
-`.claude/skills/magpie-<n>` → the same. The opposite path is
-automatically the same content via the directory symlink.
-
-Gitignore consequences: only entries on the real-directory
-side are needed (e.g. `/.github/skills/magpie-*` for D.1,
-or `/.claude/skills/magpie-*` for D.2). Git treats the
-symlinked side as a single tracked symlink and does not
-descend into it, so ignore entries on that side would match
-no actual tracked path and are unnecessary.
-
-The directory symlink itself is **adopter-owned** — created
-deliberately by the adopter as part of the project's layout
-choice, and not touched by `/magpie-setup unadopt`. The
-framework treats it the same way it treats the real-directory
-side: as part of the surrounding repo layout.
-
-**Pre-Pattern-D consolidation** — if both `.claude/skills/`
-and `.github/skills/` exist as **regular directories** (not
-yet symlinked to each other) and contain skill content that
-is not already aliased through symlinks, the adopt flow
-**does not silently apply Pattern D**. Each directory's
-contents are an independent set; turning one into a symlink
-to the other would clobber the symlinked side's content. The
-flow surfaces the conflict and offers a consolidation prompt:
-
-1. List the skills present in each directory (real
- directories, regular files, and any non-Pattern-B
- symlinks).
-2. Flag name collisions where the same skill name exists in
- both directories with different content.
-3. Ask the user to pick D.1 or D.2 and confirm the
- consolidation steps:
- - Move every skill from the side that will become the
- symlink into the side that will become the real
- directory, resolving any flagged name collisions first.
- - Replace the now-empty side with a relative symlink to
- the other side.
-4. Only after the consolidation is complete does the adopt
- flow proceed to wire framework symlinks at the chosen
- real-directory side.
-
-If the consolidation cannot proceed (unresolved name
-collisions the user has not addressed), the adopt flow stops
-and lets the user resolve in their own commit before
-re-invoking — the framework never auto-renames adopter-owned
-content.
-
-## Detection algorithm
-
-```text
-# Pattern D first — either orientation:
-if .claude/skills is a symlink:
- if it resolves to .github/skills/ in the same repo:
- pattern = D.1 (single directory symlink; canonical = .github/skills/)
- else:
- # operator pointed `.claude/skills` somewhere else
- # deliberately; surface, do not guess.
- pattern = ambiguous → prompt the user
-elif .github/skills is a symlink:
- if it resolves to .claude/skills/ in the same repo:
- pattern = D.2 (single directory symlink; canonical = .claude/skills/)
- else:
- # same — surface the unexpected target, do not guess.
- pattern = ambiguous → prompt the user
-
-# Otherwise fall through to A / B / C:
-elif .claude/skills/ exists (regular directory):
- if any entry in .claude/skills/ is a symlink resolving
- into .github/skills/:
- pattern = B (double-symlinked)
- else:
- if .github/skills/ also exists as a regular directory
- with independent content:
- pattern = ambiguous → propose Pattern D
- consolidation (see *Pre-Pattern-D
- consolidation* under section D
- above), with A as the fallback
- if the user declines
- else:
- pattern = A (flat)
-elif .github/skills/ exists:
- pattern = B (the user has a `.github/skills/` half but
- hasn't wired up `.claude/` yet — the adopt
- flow will create the .claude/ side as part
- of installing framework skills)
-else:
- pattern = C (none yet — default to A unless user picks
- otherwise)
-```
-
-## What the adopt flow does per pattern
-
-| Pattern | `<adopter-skills-dir>` (where framework symlinks land) | Side
effect |
-|---|---|---|
-| A — flat | `.claude/skills/` | None |
-| B — double-symlinked | `.github/skills/` (the inner layer);
`.claude/skills/` symlinks to it | If `.github/skills/<n>` for a framework
skill already exists as a real directory (an old in-repo copy), refuse and let
the user resolve |
-| C — none yet | `.claude/skills/` | Create the directory |
-| D.1 — single directory symlink, canonical `.github/skills/` |
`.github/skills/` (the only layer; `.claude/skills` resolves into it via the
directory symlink) | None — no outer-layer plumbing to create |
-| D.2 — single directory symlink, canonical `.claude/skills/` |
`.claude/skills/` (the only layer; `.github/skills` resolves into it via the
directory symlink) | None — no outer-layer plumbing to create |
-
-## Ambiguous cases
-
-- **The repo has both `.claude/skills/` and `.github/skills/`
- but neither contains symlinks linking the two**. This is a
- half-migrated state. The adopt flow surfaces it and asks
- the user which pattern they want; it does not guess.
-- **Pattern B but with absolute symlinks** (rather than
- relative). The adopt flow will use *relative* symlinks for
- consistency. If the user wants absolute, they say so;
- otherwise relative is the default — it survives a repo
- move.
-- **`.claude/skills` (or `.github/skills`) is a symlink but
- resolves outside the repo or to a path other than the
- expected counterpart directory**. The operator pointed it
- somewhere deliberately (e.g. a sibling worktree). The
- adopt flow surfaces the resolved target and asks the user;
- it does not match Pattern D automatically.
-- **Both `.claude/skills/` and `.github/skills/` exist as
- regular directories with independent (non-aliased)
- content**. Surfaced as a Pattern D consolidation
- opportunity per the **Pre-Pattern-D consolidation** flow
- under section D above. The user picks D.1 or D.2 (or
- declines, in which case the flow falls back to Pattern A
- treating `.claude/skills/` as canonical).
diff --git a/.github/skills/prepare-providers-documentation
b/.github/skills/prepare-providers-documentation
new file mode 120000
index 00000000000..1eadc05fae1
--- /dev/null
+++ b/.github/skills/prepare-providers-documentation
@@ -0,0 +1 @@
+../../.agents/skills/prepare-providers-documentation
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 250beef1e1e..a0ccf9fd3de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,11 +132,13 @@ ENV/
*.iml
# Claude Code — ignore local config/caches/worktrees, but keep the
-# tracked symlinks in .claude/skills/ that point at .github/skills/.
+# tracked symlinks in .claude/skills/ that point at .agents/skills/.
.claude/*
!.claude/skills/
.claude/skills/*
!.claude/skills/aip-user-stories
+!.claude/skills/airflow-translations
+!.claude/skills/prepare-providers-documentation
!.claude/skills/magpie-setup
# Kiro
@@ -332,6 +334,8 @@ dev/registry/providers.json
# (every framework skill is namespaced under the magpie- prefix).
# The committed magpie-setup bootstrap is kept tracked by the
# negation lines (the magpie-* glob would otherwise ignore it).
+/.agents/skills/magpie-*
+!/.agents/skills/magpie-setup
/.claude/skills/magpie-*
!/.claude/skills/magpie-setup
/.github/skills/magpie-*
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b5d0226dd41..f115bf85b50 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -168,6 +168,7 @@ repos:
(?x)
^\.github/.*\.md$|
^\.claude/|
+ ^\.agents/|
^(?:.*/)?AGENTS\.md$|
^(?:.*/)?CLAUDE\.md$|
^(?:.*/)?SKILL\.md$|
@@ -186,13 +187,14 @@ repos:
(?x)
^\.github/.*\.md$|
^\.claude/|
+ ^\.agents/|
^(?:.*/)?AGENTS\.md$|
^(?:.*/)?CLAUDE\.md$|
^(?:.*/)?SKILL\.md$
exclude:
(?x)
^scripts/ci/license-templates/|
- ^\.github/skills/magpie-setup/
+ ^\.agents/skills/magpie-setup/
- id: insert-license
name: Add license for all other files
args:
@@ -299,7 +301,7 @@ repos:
- 'black==26.1.0'
exclude: >
(?x)
- ^\.github/skills/aip-user-stories
+ ^\.agents/skills/aip-user-stories
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
@@ -396,8 +398,8 @@ repos:
^.*pnpm-lock\.yaml$|
.*/dist/.*|
^airflow-core/src/airflow/ui/public/i18n/locales/(?!en/).+/|
- ^\.github/skills/airflow-translations/|
- ^\.github/skills/magpie-setup/|
+ ^\.agents/skills/airflow-translations/|
+ ^\.agents/skills/magpie-setup/|
^scripts/docker/keys/.*\.asc$
args:
- --ignore-words=docs/spelling_wordlist.txt
@@ -442,7 +444,7 @@ repos:
^\.build/|
^generated/|
^\.apache-magpie-overrides/|
- ^\.github/skills/magpie-setup/
+ ^\.agents/skills/magpie-setup/
- repo: local
# Note that this is the 2nd "local" repo group in the
.pre-commit-config.yaml file. This is because
# we try to minimize the number of passes that must happen to apply some
of the changes
@@ -680,6 +682,8 @@ repos:
^providers/common/ai/src/airflow/providers/common/ai/plugins/www/pnpm-lock\.yaml$|
^airflow-core/src/airflow/ui/public/i18n/locales/de/README\.md$|
^airflow-core/src/airflow/ui/src/i18n/config\.ts$|
+ ^\.agents/skills/airflow-translations/|
+ ^\.agents/skills/magpie-setup/|
^airflow-core/src/airflow/utils/db\.py$|
^airflow-core/src/airflow/utils/trigger_rule\.py$|
^airflow-core/tests/|
@@ -887,7 +891,7 @@ repos:
files: >
(?x)
^airflow-core/src/airflow/ui/public/i18n/locales/en/.*\.json$|
- ^\.github/skills/airflow-translations/SKILL\.md$
+ ^\.agents/skills/airflow-translations/SKILL\.md$
pass_filenames: false
- id: update-pyproject-toml
name: Update Airflow's meta-package pyproject.toml
@@ -959,7 +963,7 @@ repos:
exclude: |
(?x)
^\.apache-magpie-overrides/|
- ^\.github/skills/magpie-setup/
+ ^\.agents/skills/magpie-setup/
additional_dependencies: ['[email protected]']
- id: lint-json-schema
name: Lint JSON Schema files
diff --git a/dev/README_RELEASE_PROVIDERS.md b/dev/README_RELEASE_PROVIDERS.md
index 9b222ed7a0f..efe187921bb 100644
--- a/dev/README_RELEASE_PROVIDERS.md
+++ b/dev/README_RELEASE_PROVIDERS.md
@@ -220,14 +220,14 @@ options:
Other MCP-compatible agentic clients should work as long as the GitHub MCP
server is wired up and
the framework loads `SKILL.md` files from the `.claude/skills/` discovery path.
-The skill source of truth lives in
[`.github/skills/prepare-providers-documentation/SKILL.md`](../.github/skills/prepare-providers-documentation/SKILL.md).
+The skill source of truth lives in
[`.agents/skills/prepare-providers-documentation/SKILL.md`](../.agents/skills/prepare-providers-documentation/SKILL.md).
Both Claude Code and OpenAI Codex CLI discover project-local skills via a
symlink at
`.claude/skills/prepare-providers-documentation`. If your local checkout
doesn't have that symlink
(the `.claude/` directory is gitignored), set it up once:
```shell script
mkdir -p .claude/skills
-ln -s ../../.github/skills/prepare-providers-documentation
.claude/skills/prepare-providers-documentation
+ln -s ../../.agents/skills/prepare-providers-documentation
.claude/skills/prepare-providers-documentation
```
Before invoking the skill, set the environment variable ``RELEASE_DATE`` to
the date of the release,
diff --git a/scripts/ci/prek/sync_translation_namespaces.py
b/scripts/ci/prek/sync_translation_namespaces.py
index 1dd9df6eb61..cfc309f71ad 100755
--- a/scripts/ci/prek/sync_translation_namespaces.py
+++ b/scripts/ci/prek/sync_translation_namespaces.py
@@ -26,7 +26,7 @@ from common_prek_utils import AIRFLOW_ROOT_PATH,
insert_documentation
EN_LOCALE_DIR = (
AIRFLOW_ROOT_PATH / "airflow-core" / "src" / "airflow" / "ui" / "public" /
"i18n" / "locales" / "en"
)
-SKILL_FILE = AIRFLOW_ROOT_PATH / ".github" / "skills" / "airflow-translations"
/ "SKILL.md"
+SKILL_FILE = AIRFLOW_ROOT_PATH / ".agents" / "skills" / "airflow-translations"
/ "SKILL.md"
START_MARKER = "<!-- START namespace-files, please keep comment here to allow
auto update -->"
END_MARKER = "<!-- END namespace-files, please keep comment here to allow auto
update -->"