Confirms the framework is wired in correctly so the rest of the framework's skills resolve from the right paths, and surfaces any drift between the committed lock (project pin) and the local lock (per-machine fetch). Read-only by default — surfaces gaps and remediation commands.
--auto-fix-symlinks— exception to read-only. If the snapshot is present but symlinks are missing or dangling in any active target dir (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.mdStep 10) on a fresh worktree where the gitignored symlinks didn't follow the checkout.
git rev-parse --show-toplevel— must succeed.- Framework checkout? Detect structurally (as in
adopt.mdStep 0):skills/setup/SKILL.mdexists at the repo root withname: magpie-setupandskills/list-skills/is present. If so and.apache-magpie.lockrecordsmethod: local, the repo is self-adopted — run the Local self-adoption checks instead of the snapshot checks below, then stop. A framework checkout with nomethod: locallock is simply not adopted yet — point at/magpie-setup. - If
<repo-root>/.apache-magpie.lockis missing, the repo is not adopted. Surface and stop with a pointer at/magpie-setup adopt.
Run these (and only these) when pre-flight detected a framework
checkout self-adopted with method: local. There is no snapshot,
no remote lock, and no per-machine lock to drift against — the
committed symlinks into the in-repo skills/ source are the
adoption state.
- Marker lock.
.apache-magpie.lockparses and recordsmethod: local. ✓ when present; ✗ with a pointer at/magpie-setupotherwise. - Symlinks resolve (canonical → source, relays → canonical).
In the canonical dir
.agents/skills/, everymagpie-<n>is a symlink whose target (../../skills/<n>/) resolves to a directory containingSKILL.md. In every relay target dir (agents.md—.claude/skills/,.github/skills/, plus any present holdout), everymagpie-<n>is a symlink whose target (../../.agents/skills/magpie-<n>) resolves through the canonical entry to the sameSKILL.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). - Coverage. Every
skills/<n>/with aSKILL.mdhas a canonicalmagpie-<n>symlink in.agents/skills/and a matching relay in every other active target dir (unless askill-families:filter was deliberately applied). ⚠ list any source skill with no link, per target; remediation:/magpie-setup. .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).- 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 not cleaned up).
Run all checks even on early failure (a missing snapshot at check 1 doesn't tell us anything about the override directory or doc updates — surface every check).
<snapshot-dir>/ exists (as a directory or a symlink that
resolves to one) and contains the expected top-level files
(README.md, AGENTS.md, .claude/skills/, tools/).
- ✗ if missing and we are in the main checkout (
git rev-parse --git-direqualsgit rev-parse --git-common-dir) → run/magpie-setup upgrade(it gracefully handles the recover-snapshot case when the committed lock exists but the snapshot does not). - ✗ if missing and we are in a worktree (the two dirs
differ) → run
/magpie-setup worktree-initto symlink<snapshot-dir>to the main checkout's. Do not proposeupgrade— that creates a per-worktree snapshot, which is the bugworktree-initis designed to prevent. - ⚠ if present as a regular directory in a worktree →
legacy per-worktree snapshot. Suggest
/magpie-setup worktree-init(with the move-aside flow) to convert into a symlink to the main's snapshot. Verify continues — the per-worktree snapshot is still functional, just wasteful. - ✗ if missing top-level files → snapshot is corrupted; same remediation as the missing-snapshot case above.
- ⚠ if
<snapshot-dir>is a symlink that resolves outside the same repo's main checkout — the operator pointed it at a different framework checkout deliberately. Surface the resolved target and continue; do not auto-remediate.
<committed-lock> (.apache-magpie.lock) and
<local-lock> (.apache-magpie.local.lock) both parse.
- ✗ if
<committed-lock>is missing → not adopted; redirect (already caught in pre-flight). - ⚠ if
<local-lock>is missing or unparsable → first install on this machine has not run, or the file was truncated. Suggest/magpie-setup upgradeto re-create the snapshot + write the local lock.
This is the core drift check. The same logic every framework skill runs at the top of its invocation.
Compare:
<committed-lock>.methodvs<local-lock>.source_method<committed-lock>.urlvs<local-lock>.source_url<committed-lock>.refvs<local-lock>.source_ref- For
git-branch: also compare upstream tip (the actual currentHEADof the branch on the remote) against<local-lock>.fetched_commit.
| Result | Severity |
|---|---|
All match (and for git-branch, local is at upstream tip) |
✓ |
| Method or URL differ | ✗ — full re-install needed; remediation: /magpie-setup upgrade |
Ref differs (e.g. project bumped tag, or git-branch local is behind upstream) |
⚠ — sync needed; remediation: /magpie-setup upgrade |
svn-zip SHA-512 differs from the verification anchor in <committed-lock> |
✗ — security-flagged; the released zip changed content; investigate before upgrading |
Check that the entries from
adopt.md Step 7 are present in
<repo-root>/.gitignore. Required:
/.apache-magpie/(snapshot path)/.apache-magpie.local.lock(per-machine state)/.claude/settings.local.json(per-machine project-scope settings — written to bysandbox-add-project-root.shas the per-worktree sandbox-allowlist defense for issue #197; must never be committed since the content is machine-specific absolute paths)__pycache__/and*.pyc(byte-compiled artefacts emitted when framework skill scripts run from the adopter checkout; non-anchored so they match at any depth)
Recommended (a uniform magpie-* glob block per active
target dir — 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. -
✗ if
/.apache-magpie.local.lockis not gitignored — per-machine state would leak into the repo. -
✗ if
/.claude/settings.local.jsonis not gitignored — per-machine absolute paths would leak into the repo; the sandbox-allowlist helper refuses to write to a non-ignored target as defense in depth, butverifysurfaces the underlying.gitignoregap so the operator fixes the root cause. -
⚠ if
__pycache__/or*.pycis not gitignored — byte-compiled artefacts from skill scripts could be accidentally committed. -
⚠ if symlink patterns are not gitignored.
Run this check across every active target dir
(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 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 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-*exceptsetupitself, and everylist-*— perSKILL.mdGolden rule 8) → surface as ✗. These families are not opt-in; missing symlinks here indicate a broken install or a skipped upgrade pass. Remediation:/magpie-setup verify --auto-fix-symlinks(cheap), or/magpie-setup upgrade(covers the family-wide pass). - Opt-in family the project picked (per
<committed-lock>/<local-lock>) → surface as ✗. The project declared the family but the install is missing a skill from it. Remediation as above. - Opt-in family the project did NOT pick → surface as ⚠. The user may have intentionally not picked that family; the warning prompts a decision.
The --auto-fix-symlinks path repairs the first two
classes in place — in every active target dir — without
prompting; the ⚠ class needs an explicit /magpie-setup adopt
re-run with the family added to the pick.
<repo-root>/.apache-magpie-overrides/ is a directory
with the README.md scaffold from
adopt.md Step 9.
- ✗ if missing →
/magpie-setup adopt(idempotently re-creates). - ⚠ if present but
README.mdis missing — the directory may have been hand-created. Suggest re-running/magpie-setup adopt.
Compare the canonical committed setup skill
(at .agents/skills/magpie-setup/) against the
snapshot's .apache-magpie/skills/setup/.
-
✓ if same content.
-
⚠ if different — the adopter's committed copy has drifted from the snapshot. The remediation depends on which way the drift goes:
- Snapshot is newer than the committed copy (typical
case after a framework upgrade where the adopter has
not yet rerun
/magpie-setup upgrade). Run/magpie-setup upgrade— its Step 4b auto-overwrites the committed copy with the snapshot's version, reloads the skill in-flight so the rest of the upgrade run executes against the new bootstrap content (perSKILL.mdGolden rule 9), surfaces local modifications first if any exist, and lands the change ingit statusfor the user to commit. - Committed copy is newer than the snapshot (the
adopter modified the bootstrap skill directly; an
anti-pattern per the framework's hard rule). The
framework-side fix is to upstream the modifications as
a PR against
apache/magpie; the local fix is to revert the modifications and use.apache-magpie-overrides/instead.
- Snapshot is newer than the committed copy (typical
case after a framework upgrade where the adopter has
not yet rerun
Two sub-checks on <repo-root>/.git/hooks/post-checkout:
-
Presence + executable. File exists, is executable, and carries the current hook body — the sandbox-allowlist helper chain and the agent-guard seeding block (see
adopt.mdStep 10). It must not contain the long-removed/magpie-setup verify --auto-fix-symlinksline (a slash command is not shell-callable; it printed a spurious error on every checkout).- ⚠ if missing — strictly optional, but worktrees off this
repo will then not get their sandbox allowlist or
agent-guard seeded automatically on
git worktree add(they fall back to/magpie-setup worktree-init). Print the install recipe.
- ⚠ if missing — strictly optional, but worktrees off this
repo will then not get their sandbox allowlist or
agent-guard seeded automatically on
-
Content drift vs the framework's expected. Diff the installed hook against the framework's expected hook content (the canonical source is shipped under the snapshot — locate it during the check). Same logic applies for any other adopter-installed local hook or config file the framework grows in future.
- ✓ if content matches.
- ⚠ if drifted and the diff looks like operator
hand-edits — surface the diff; remediation is to run
/magpie-setup(adopt or upgrade), whose hook+config-sync pass re-installs from the snapshot after asking about hand-edits. - ✗ if drifted and the installed content is clearly stale (older framework version's recipe) — same remediation, no operator prompt needed; the sync pass overwrites silently.
Three sub-checks for the deterministic guard
(tools/agent-guard):
- Script present + matches the snapshot.
<repo-root>/.claude/hooks/agent-guard.pyexists and its content matches the snapshot'stools/agent-guard/src/agent_guard/__init__.py.- ⚠ / ✗ on missing / stale — remediation is
/magpie-setup(adopt or upgrade), whose sync pass re-installs it.
- ⚠ / ✗ on missing / stale — remediation is
guards.dpopulated.<repo-root>/.claude/hooks/guards.d/exists and contains every guard the snapshot ships — the engine's bundledguards.d/*.pyand each skill-ownedskills/*/guards/*.py(e.g.mention,mark_ready,security_language). Flag a missing expected guard or a stale copy; extra locally-added*.pyare fine. A missing skill guard means that skill's deterministic protection is silently inactive — remediation is/magpie-setup(adopt/upgrade), which re-collects.- Hook wired in settings.json.
<repo-root>/.claude/settings.jsonhas ahooks.PreToolUseentry (matcherBash) whose command runsagent-guard.py.- ⚠ if missing — the script is present but not active; print
the one-time wiring snippet (see
adopt.mdStep 12) for the maintainer to apply (settings.json is agent-edit-denied).
- ⚠ if missing — the script is present but not active; print
the one-time wiring snippet (see
The script + guards.d are gitignored framework code
(adopt.md Step 7),
synced from the snapshot rather than committed — so a missing
script is the expected state of a fresh checkout, not a defect, and
the fix is always a re-sync (never git add). When this check runs
inside a worktree, the script + guards.d are per-worktree
files (the settings.json wiring resolves
$CLAUDE_PROJECT_DIR/.claude/hooks/agent-guard.py against the
worktree root). The remediation for a missing script in a worktree
is not the main-checkout sync but
worktree-init.md Step 1d
(or the post-checkout hook on the next git worktree add), which
seeds it from the main checkout's already-synced copy.
Defensive cross-check for
issue #197:
sandbox.filesystem.allowRead: ["."] does not in practice cover
CWD under the harness, so /magpie-setup (adopt, upgrade,
worktree-init) chains into
~/.claude/scripts/sandbox-add-project-root.sh to add explicit
absolute paths to each worktree's own project-local settings.
This check verifies that chain landed for the current worktree.
For the current worktree (resolved via
git rev-parse --show-toplevel):
- ✓ if the absolute path appears in both
<worktree>/.claude/settings.local.json'ssandbox.filesystem.allowReadandsandbox.filesystem.allowWrite. - ✗ if missing from either array, and the helper script
~/.claude/scripts/sandbox-add-project-root.shis installed — remediation:~/.claude/scripts/sandbox-add-project-root.sh(no--all-worktreesneeded — just this worktree), or re-run/magpie-setup(adopt/upgrade) which chains into the helper as part of its Step 12 / Step 6c sandbox-allowlist pass. - ⚠ if missing from either array and the helper script is
absent — the operator has not run
/magpie-setup-isolated-setup-installyet. 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.jsonis absent entirely — same remediation (re-run the helper or/magpie-setup-isolated-setup-install). The file is auto-created by the helper on first run. - ✗ if
<worktree>/.claude/settings.local.jsonexists AND is not gitignored (cross-check viagit check-ignore). Per the security rationale indocs/setup/secure-agent-setup.md→ Security rationale — why project-local is safe to write to, the per-machine settings.local.json must never be committed. Remediation: add/.claude/settings.local.jsonto the adopter's.gitignore(also surfaced by check 4 above).
The check scopes to the current worktree only, not the full
git worktree list, because each worktree carries its own
project-local settings file — /magpie-setup verify running
in worktree A has no business asserting on worktree B's file
(which it cannot even reliably read without crossing into
another working tree's path).
This check is read-only on the framework state. The defence
is layered: /magpie-setup writes during adopt/upgrade,
setup-isolated-setup-verify adds a live read+write probe
(check 8 there), and this check is the cheap static cross-check
to surface drift between the two skill families.
Detect worktrees the agent (or a prior session) created under
<repo-root>/.claude/worktrees/ that have been left lying around
beyond their useful life. Main-checkout only — worktrees can
only be inspected from the checkout that owns them, and the
git worktree list output is the same across the family anyway.
Stale agent-worktrees are a real friction source: they hold
branches (typically main, since EnterWorktree defaults to
branching from main), so a subsequent git checkout main from
the main checkout fails with "main is already used by worktree
at …" — silently, in the middle of a longer command pipeline,
producing confusing downstream failures. A session that ended
without explicit ExitWorktree(action: "remove") leaves the
worktree on disk; the next session has no way to know it is
abandoned.
The check:
-
Run
git worktree list --porcelainand filter to entries whoseworktreepath is under<repo-root>/.claude/worktrees/. -
For each, compute the age — the maximum of:
- the worktree directory's
mtime(file-system signal — how long since anything inside changed); and git -C <worktree> log -1 --format=%cI HEAD's commit time (git-state signal — how recent the latest commit on the worktree's branch is).
The max-of-two avoids two failure modes: a worktree whose commits are old but whose files were touched recently (still active) and a worktree whose files are old but whose branch was recently rebased (still in use). Both look fresh to one of the signals alone.
- the worktree directory's
-
Bucket the result against a threshold (default: 7 days; adopter override via
worktree_stale_daysin<project-config>/magpie-setup.md— if absent, default stands):- ✓ if age ≤ threshold
- ⚠ if age > threshold AND the worktree has zero
uncommitted changes (
git -C <worktree> status --porcelainis empty) — surface the path, age, branch name, and proposegit worktree remove <path>as the cleanup. - ✗ if age > threshold AND the worktree has uncommitted
changes — surface the same info plus an explicit
"uncommitted changes present" warning, and propose
two-step cleanup: first commit-or-stash, then
git worktree remove --force <path>(orEnterWorktree(path)to enter it interactively and decide).
-
The check is read-only: it never auto-removes a worktree, never force-anything. The proposal lands in the verify-report and the operator chooses to act.
Threshold rationale. Agent-worktrees are designed for
per-task isolation: open, work, close. A worktree older than
7 days is overwhelmingly a session that ended without explicit
cleanup. Lower thresholds (3 days, 1 day) hit false-positive
on multi-day tasks that legitimately stretch across sessions;
higher thresholds (14, 30 days) let the bug class persist
long enough to actually break a git checkout main weeks
later.
Why this check exists separately from worktree-init.
worktree-init wires up a newly-created worktree. There is
no symmetric step for end-of-life: EnterWorktree(action: "remove") from inside a session removes it cleanly, but
sessions that crash, get interrupted, or end via context-
window-exhaustion leak. This check is the periodic cleanup
sweep that catches the leakage.
Audit the adopter's per-machine permission allow-list for
patterns that grant arbitrary code execution, and surface
the recommended read-only patterns the framework's skills
use heavily. Local-state only — the framework never
mutates .claude/settings*.json; this check produces
proposals the operator confirms before any write.
Two files to read:
<repo-root>/.claude/settings.json(committed, project-wide).<repo-root>/.claude/settings.local.json(gitignored, per-machine — same security model as.apache-magpie.local.lock).
For each, parse the JSON, walk permissions.allow[], and
bucket each entry against two canonical lists.
Forbidden — propose removal (✗ per entry hit): broad wildcards over interpreters, shells, and package runners. Treat any of the following allow-list strings as an arbitrary-code-execution hole, regardless of how the adopter justified adding them:
Bash(python *),Bash(python3 *),Bash(node *),Bash(bun *),Bash(deno *),Bash(ruby *),Bash(perl *),Bash(php *),Bash(lua *)Bash(bash *),Bash(sh *),Bash(zsh *),Bash(fish *),Bash(eval *),Bash(exec *),Bash(ssh *)Bash(npx *),Bash(bunx *),Bash(uvx *),Bash(uv run *)Bash(npm run *),Bash(yarn run *),Bash(pnpm run *),Bash(bun run *),Bash(make *),Bash(just *),Bash(cargo run *),Bash(go run *)Bash(gh api *),Bash(docker run *),Bash(docker exec *),Bash(kubectl exec *),Bash(sudo *)
The list mirrors the "Never allowlist a pattern that
grants arbitrary code execution" rule from Claude Code's
user-level /fewer-permission-prompts slash command — the
framework's copy lives here so adoption itself is not
silently contingent on a sibling skill being present.
It is not exhaustive: an allow-list entry that fits
the same category (anything that can spawn an arbitrary
process or shell out via a flag) is a ✗ even if its exact
token does not appear above.
Recommended — propose addition (⚠ per entry missing):
narrow read-only patterns the framework's skills invoke
often. An adopter who picks up the security family will
hit these constantly; pre-allowing them removes the
repetitive confirmation prompts without weakening the
boundary. Tailor the recommendation to the families the
adopter opted into via
<committed-lock> → skill-families:
-
securityfamily —mcp__claude_ai_Gmail__get_threadmcp__claude_ai_Gmail__search_threadsmcp__claude_ai_Gmail__list_draftsmcp__claude_ai_Gmail__list_labelsmcp__gmail-plaintext__create_draftmcp__ponymail__search_listmcp__ponymail__auth_statusmcp__ponymail__get_threadmcp__ponymail__get_emailmcp__ponymail__list_restrictionsmcp__apache-projects__project_statsmcp__apache-projects__get_committeemcp__apache-projects__get_group_membersmcp__apache-projects__get_personmcp__apache-projects__search_peopleBash(vulnogram-api-record-fetch *)
(The
mcp__apache-projects__*read tools back the roster / affiliation lookups — also used bycontributor-nomination, the maintainer-side /committer assessment skill. Both MCP servers are installed from the latestmainofapache/comdev; seetools/apache-projects/tool.mdandtools/ponymail/tool.md.) -
Any family that ships docs / markdown (effectively every adopter, since the framework itself ships docs) —
Bash(lychee *)— read-only link-checker invoked by the "run lychee before pushing a PR" hygiene gate documented inAGENTS.md.
The recommended list is deliberately narrow — every
entry is read-only, scoped to a specific tool, and
verified against Claude Code's auto-allowed harness
exclusions (READONLY_COMMANDS, GIT_READ_ONLY_COMMANDS,
GH_READ_ONLY_COMMANDS, etc.) so the framework does not
redundantly propose entries that never prompt anyway.
Implementation. The classification logic and the atomic
edit path are factored out into the
tools/permission-audit
CLI; the canonical forbidden + recommended-by-family lists
live in
tools/permission-audit/src/permission_audit/audit.py.
The skill invokes the CLI once per settings file:
uv run --project <framework>/tools/permission-audit \
permission-audit audit <repo>/.claude/settings.local.json \
--families <comma-joined families from the lock>The CLI emits structured JSON the skill folds into the verify
report. Exit code 1 from the CLI maps to ✗ on this check.
Reporting shape: group findings by file, then by bucket.
For each forbidden entry, print the exact JSON-pointer-style
path (.permissions.allow[<index>]) the CLI returned so the
operator can locate it instantly; for each recommended entry
missing, print the suggested string verbatim ready for paste.
Do not auto-write the files — the per-machine
settings.local.json is the operator's; surface the proposal
and let /magpie-setup verify --apply-permission-audit
(interactive) or a hand-edit close the gap. The apply path
calls
uv run --project <framework>/tools/permission-audit \
permission-audit apply <repo>/.claude/settings.local.json \
--add '<entry>' --remove '<entry>' ...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.replaces into place — so concurrent
/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
settings files typically are), the apply path requires the
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
/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 the operator's capability surface for the agent in this checkout. Even an objectively-safer edit (drop a known-dangerous wildcard) is a capability change the operator must own, both to know it happened and to keep the audit trail human-readable. The framework's job is to surface the gap — the operator's job is to close it.
Run this check only for ASF projects — detect ASF the same way
as adopt.md Step 9c:
<project-config>/project.md declares project_metadata.mandatory: true or Mail sources ponymail mandatory: yes. Skip otherwise
(the two MCP servers are optional for non-ASF adopters).
For ASF projects, both the
PonyMail and
Apache Projects MCP
servers are mandatory pre-flight prerequisites, installed from the
latest main of apache/comdev (tracked, not pinned). Confirm:
- Registered.
mcp__ponymail__*andmcp__apache-projects__*appear in the session tool list. ✗ on either missing — the mandatory pre-flight gates insecurity-issue-import/security-issue-sync(PonyMail) andcontributor-nomination(Apache Projects) will hard-stop. Remediation:adopt.mdStep 9c. - PonyMail authenticated. For ASF projects an authenticated
LDAP session is required, not just a registered server — a
trivial
mcp__ponymail__auth_status()should report an authenticated session. ⚠ if registered but unauthenticated (remediation:mcp__ponymail__login()). - Checkout on
main, current. Resolve each server's checkout root from itsmcpServersargspath and confirmoriginisapache/comdev, the branch ismain, and it is not behind the last-fetchedorigin/main. This is the read-only, offline form of the freshness assertion; the authoritative live fetch belongs to/magpie-setup upgradeStep 6e andsetup-isolated-setup-update. ✗ off-mainor non-apache/comdevremote; ⚠ behindorigin/main.
Two files to check (per
adopt.md Step 11):
<repo-root>/README.md— should have a contributor-facing section (typically## Agent-assisted contribution (apache-magpie)) that mentions the snapshot mechanism, the/magpie-setupinvocation for fresh clones, the.apache-magpie.lockpin, and.apache-magpie-overrides/. Grep forapache-magpieand/magpie-setuptogether as a proxy. ⚠ if either token is absent.<repo-root>/AGENTS.md— if the file exists, it should have an## apache-magpie frameworksection that cross-references the README section. Grep forapache-magpieand a link to the README anchor. ⚠ if the file exists but lacks the section; not applicable if the file does not exist (do not create one just to satisfy the check).
Cheap to skip if both are absent on a minimal repo — surface
as ⚠ overall only, never ✗. CONTRIBUTING.md counts as a
fallback for README.md if the adopter declared it so during
adoption.
Only when <project-config>/skill-sources.md (the trust list)
lists at least one source — otherwise skip this check silently
(the adopter runs in-tree skills only). For each trusted source
(skill-sources.md):
- Committed pin present. The source has a block in
.apache-magpie.sources.lock(method/url/ref+ anchor). Missing ⇒ ✗: the trust list vouches for a source that was never pinned — run/magpie-setup skill-sources. - Snapshot present.
.apache-magpie-sources/<id>/exists on disk with the source'sskills_root. Missing ⇒ ✗ with the remediation/magpie-setup skill-sources(the fetch is gitignored, so a fresh clone has none — expected, same as the framework snapshot). - Source drift. The source's committed block vs its
.apache-magpie.sources.local.lockblock — a mismatch ⇒ ⚠ and proposes/magpie-setup upgrade, exactly like framework drift (check 3). - Symlinks live. Every
magpie-<name>the sourceprovidesresolves through the canonical.agents/skills/magpie-<name>→../../.apache-magpie-sources/<id>/skills/<name>/and its relays (same rule as check 5). Dangling / misdirected ⇒ ✗ with/magpie-setup verify --auto-fix-symlinks. - No name collision. No
magpie-<name>provided by a source shadows a framework skill or another source's skill. Collision ⇒ ✗ (surface, do not auto-resolve).
If every check is ✓ (or ⚠ on items the adopter has intentionally opted out of), say so explicitly and stop.
If anything is ✗, end the report with a concrete next-step list, ordered most → least urgent:
- ✗ on check 1 →
/magpie-setup upgrade(re-fetches per the committed lock). - ✗ on check 3 (drift) →
/magpie-setup upgrade. - ✗ on check 5 (dangling symlinks) →
/magpie-setup verify --auto-fix-symlinks(cheap; no-op when symlinks already correct). - ✗ on check 6 →
/magpie-setup adopt(idempotent re-create). - ✗ on check 4 / SHA-512 mismatch → investigate first; do not run upgrade until you understand why the released zip changed under the same version.
- ⚠ on check 8c (stale agent-worktree, no uncommitted
changes) →
git worktree remove <path>per the per-worktree proposal in the report. Idempotent; safe to batch across all flagged worktrees in one pass. - ✗ on check 8c (stale agent-worktree, uncommitted
changes present) → operator decision required. The
proposal lists each affected worktree with its branch
- diff summary; recover via
EnterWorktree(path)(orcd <path>outside the harness) to inspect, then either commit / push or stash, thengit worktree remove --force <path>. Never propose--forcewithout first surfacing the diff.
- diff summary; recover via
- ✗ on check 8d (forbidden allow-list entry — arbitrary
code execution) → propose removing the named entry from
the file's
permissions.allow[]array. Print the JSON- pointer path so the operator can locate it. Per-machinesettings.local.jsonwrites go via/magpie-setup verify --apply-permission-audit(interactive, atomic JSON edit, sandbox-bypass requires per-write authorisation). Committedsettings.jsonwrites are a regular file edit + commit; flag them loudly because they bind every developer on the project. - ⚠ on check 8d (recommended allow-list entry missing) →
optional. Print the suggested string ready for paste;
apply via the same
--apply-permission-auditflag, or paste manually. The recommendation is family-scoped, so an adopter who skipped thesecurityfamily will not see the Gmail / PonyMail entries surfaced as gaps. - ✗ on check 8e (ASF project, comdev MCP not registered or
off-
main) →/magpie-setup adoptStep 9c to (re-)install from latestapache/comdevmain. ⚠ on check 8e (PonyMail unauthenticated, or checkout behindorigin/main) →mcp__ponymail__login()and/or/magpie-setup upgradeStep 6e (live fetch +git pull --ff-only). - All other ✗ / ⚠ → name the gap, give the one-line remediation.